threesome fixed

This commit is contained in:
API runner
2026-08-14 20:55:11 +00:00
parent b6ecf6ad7c
commit b933f09187
260 changed files with 17747 additions and 270 deletions
+130
View File
@@ -0,0 +1,130 @@
# Unity dedicated server — match lifecycle (escrow + always finish)
Contract for the Kick Kings dedicated server build against the matchmaker API.
Header on all `/internal/*` calls: `X-Dedicated-Server-Secret: <secret from settings>`.
## Goals
1. Collect entry fees into a **per-match escrow** only after **both** players are connected.
2. Pay the winner (and house fee) **only from that escrow**.
3. **Never leave a match unfinished** — always report a winner, including disconnect forfeits.
## Sequence
```
1. Match row exists (matchmaking or rematch) with match_id, user_red, user_blue, entry_fee
2. Red connects → PATCH /internal/match/:matchId { "red_joined_at": "<ISO-8601 UTC>" }
3. Blue connects → PATCH /internal/match/:matchId { "blue_joined_at": "<ISO-8601 UTC>" }
→ When both join timestamps are set, the API collects entry fees into escrow
4. Start the match only after collect succeeds (see responses below)
5. Match ends (score / disconnect / crash path) → PATCH /internal/match/:matchId/winner
{ "winner": "red" | "blue" }
```
## Join PATCH
`PATCH /internal/match/:matchId`
Example bodies:
```json
{ "red_joined_at": "2026-08-12T18:00:00.000Z" }
```
```json
{ "blue_joined_at": "2026-08-12T18:00:01.000Z" }
```
### Success when both have joined (collect ran)
```json
{
"ok": true,
"id": 123,
"entries_collected": true,
"already_collected": false,
"escrow_user_id": 456
}
```
### Success when only one side joined yet
```json
{
"ok": true,
"id": 123,
"entries_collected": false
}
```
### Collect failure (fatal — do not start the match)
Typical cases:
| HTTP | Meaning |
|------|---------|
| 402 | One or both players lack enough RC for `entry_fee` |
| 409 | Players/joins incomplete (should not happen if you set both joins) |
| 503 | DB / economy RPCs not installed on the server |
On any non-2xx after the second join, **abort kickoff**, disconnect both clients, and do **not** call winner (fees were not escrowed).
Idempotent: repeating the join PATCH after a successful collect returns `entries_collected: true` (or `already_collected: true`).
## Winner PATCH
`PATCH /internal/match/:matchId/winner`
```json
{ "winner": "red" }
```
or
```json
{ "winner": "blue" }
```
Requires entry fees already collected. Response includes economy summary (`entry_fee_rc`, `rc_prize`, `participant_cc`).
| HTTP | Meaning |
|------|---------|
| 200 | Settled (or already settled for the same winner) |
| 409 | Entries not collected yet, or winner already set to the other side |
| 500 | Escrow inconsistency (should be rare; escalate) |
Call winner **exactly once** when the match outcome is known. Retries with the **same** side are safe if the first call succeeded.
## Always finish — disconnect / forfeit rules
Matches must **never** end without a winner call after fees were collected.
Track disconnect order on the dedicated server (timestamps or a queue).
1. **One player disconnects mid-match**
Remaining player wins.
Example: blue disconnects → `{ "winner": "red" }`.
2. **Both players disconnect**
The **last player who disconnected is the winner**.
Example: red disconnects first, then blue → blue was last to leave → `{ "winner": "blue" }`.
3. **Process crash / forced shutdown**
If fees were collected, choose a winner with the same rules (remaining player, or last disconnect) and call winner **before** exit whenever possible.
4. **Normal score / time win**
Report the actual winning side as today.
Do **not** leave escrow locked with no winner. Do **not** invent a draw path that skips the winner endpoint.
## Rematch
`POST /internal/rematch` still only creates the room/match row. Entry fees are **not** taken there. Collect still happens when both `*_joined_at` are set on the new `match_id`.
## Checklist for the Unity build
- [ ] Set `red_joined_at` / `blue_joined_at` in UTC ISO when each client is fully connected
- [ ] Treat 402/4xx/5xx on the second join as fatal — no kickoff
- [ ] Gate gameplay start on `entries_collected: true`
- [ ] Always call winner after collect (score, forfeit, or last-disconnect-wins)
- [ ] Persist disconnect order until winner is acknowledged