Files
kickkingsapi/docs/unity_cc_forfeit_prompt.md
2026-09-04 19:11:49 +00:00

7.3 KiB
Raw Permalink Blame History

Prompt for Unity Cursor — no CC on forfeit (including skip/auto-forfeit)

Copy everything below the line into Unity Cursor. Do not change join/collect, rematch, hidden MMR display, or player-facing UI.


You are working in the Kick Kings dedicated server Unity project.

The matchmaker already awards 100 CC only to players still connected at settle. That is not enough: a skip-forfeit / auto-forfeit player stays connected, so they still received CC. The API now also withholds CC from anyone who forfeited.

Your only job: on PATCH /internal/match/{matchId}/winner, send forfeit: true for every forfeit path — leave, disconnect, last-disconnect-wins, and skip/auto-forfeit (two skipped turns). Keep red_connected / blue_connected accurate. Do not infer them from winner. Keep the existing header X-Dedicated-Server-Secret. Call winner exactly once when the outcome is known.

Do not reimplement CC. The API decides who gets it from the flags you send.

Endpoint (unchanged URL)

PATCH /internal/match/{matchId}/winner

JSON body (already exists — fix forfeit on skip/auto-forfeit)

Always send all six fields. JSON types must be real booleans and integers (true not "true", 3 not "3").

{
  "winner": "red",
  "red_connected": true,
  "blue_connected": true,
  "red_score": 3,
  "blue_score": 1,
  "forfeit": false
}
Field Type Rules
winner "red" or "blue" Winning side. RC prize still goes here. Unchanged.
red_connected bool Red still had a live connection at settle. Do not infer from winner.
blue_connected bool Blue still had a live connection at settle.
red_score int Rounds/sets red has won (first-to-3). Send on every settle.
blue_score int Same for blue.
forfeit bool true for leave, disconnect, last-disconnect-wins, and skip/auto-forfeit. false only for a normal score win.

What changed (this is the bug)

Previous contract: skip-forfeit sent forfeit: false because the skipper was still connected. The API then paid them 100 CC.

New contract: skip-forfeit / auto-forfeit is still not a disconnect (*_connected stays true if they are in the match), but it is a forfeit. Send forfeit: true. The API then pays CC to the connected non-forfeiting side only.

How the API awards CC (do not reimplement)

Let amount = 100. Do not infer connectedness from winner. A last-disconnect winner can be disconnected: they get RC, not CC.

On any forfeit, CC is never paid to the loser.

red_connected blue_connected forfeit CC credit
true true false +amount to both
true true true +amount to the winner only (skip/auto-forfeit)
true false true +amount to red only
false true true +amount to blue only
false false true nobody

RC prize / escrow is unchanged: still paid to winner.

Connected flags (unchanged meaning)

Set a side to false when:

  • Mirror reports disconnect and reconnect grace expired
  • That player pressed Leave (even if TCP is still up for a moment)

true = still in the match when you report the outcome.

Skip-forfeit is not a disconnect. If that player is still in the match, keep their *_connected: true and send forfeit: true.

What to send in each end condition

1. Normal score win (first to 3)

Both still in the match. forfeit: false. Both get CC.

{ "winner": "red", "red_connected": true, "blue_connected": true, "red_score": 3, "blue_score": 1, "forfeit": false }

2. One player leaves or disconnects

Remaining player wins. Leaver *_connected: false. forfeit: true. Only the remaining connected player gets CC.

{ "winner": "red", "red_connected": true, "blue_connected": false, "red_score": 1, "blue_score": 0, "forfeit": true }

3. Both disconnect (last-disconnect-wins)

Last player who disconnected is winner. Both *_connected: false. forfeit: true. Nobody gets CC. RC still goes to winner.

{ "winner": "blue", "red_connected": false, "blue_connected": false, "red_score": 0, "blue_score": 2, "forfeit": true }

4. Skip-forfeit / auto-forfeit (two skipped turns) — this is the change

Not a disconnect. Keep *_connected: true if they are still in the match. Send forfeit: true. Winner is whoever the game rules already pick. The skipper (loser) does not get CC. The opponent gets CC if connected.

{ "winner": "red", "red_connected": true, "blue_connected": true, "red_score": 1, "blue_score": 0, "forfeit": true }

5. Process crash / forced shutdown after fees were collected

Same as remaining-player or last-disconnect. Send accurate *_connected, scores, and forfeit: true if it was a disconnect/forfeit path. Call winner before exit whenever possible.

Never skip the winner call after collect. Never invent a draw.

Success response (use cc_awarded_* as today)

{
  "ok": true,
  "id": 123,
  "winner": "red",
  "winner_id": 456,
  "already_settled": false,
  "economy": {
    "entry_fee_rc": 21,
    "rc_prize": 34,
    "participant_cc": 100,
    "cc_awarded_red": true,
    "cc_awarded_blue": false
  },
  "mmr_delta": 27,
  "forfeit": true
}
  • Keep using economy.cc_awarded_red / cc_awarded_blue as today.
  • mmr_delta and forfeit on the response are for server logs only. Do not forward them to player clients. Do not show them in UI.
  • Honor response winner / winner_id. On simultaneous Leave/skip, a second forfeit: true PATCH returns 200 already_settled: true with the first settles winner, not 409.

HTTP: 200 = settled, already settled same winner, or a second forfeit that lost the race. 409 = entries not collected, or winner already set to the other side on a non-forfeit conflict.

Where to change

Find the dedicated-server method that PATCHes /internal/match/{id}/winner.

Today skip-forfeit / two skipped turns likely sets forfeit = false because the player is still connected. Change that path so forfeit = true.

Leave, disconnect, and last-disconnect should already send forfeit: true. Do not regress those.

Payload shape is unchanged:

public sealed class WinnerReportBody
{
    public string winner;          // "red" | "blue"
    public bool red_connected;
    public bool blue_connected;
    public int red_score;
    public int blue_score;
    public bool forfeit;
}

Do not

  • Set forfeit: false for skip-forfeit / auto-forfeit
  • Set *_connected: false for skip-forfeit just to kill CC (the player is still in the match; that would also switch hidden MMR to the 20% disconnect path)
  • Infer red_connected / blue_connected from who won
  • Change join PATCH (red_joined_at / blue_joined_at) or rematch
  • Show MMR, mmr_delta, or CC award flags in player UI unless they are already shown
  • Skip the winner call after fees were collected

Done when

  • Normal score wins still send forfeit: false
  • Leave / disconnect / last-disconnect still send forfeit: true and *_connected: false on leavers
  • Skip-forfeit / auto-forfeit sends forfeit: true with accurate *_connected (usually both true)
  • Skipper no longer receives CC; opponent still does if connected
  • Winner is still always called after fees were collected