# Period battles

## 1. Calendar and admission

Daily, weekly and monthly battles share a leaderboard across participating sites. Each player may enter each period once. Identity is the persistent account created from the site's immutable `externalId`. Another device, a new session or a retried request does not create another purchase. Accounts on different sites remain separate; the system does not assume they belong to the same real-world person.

Periods use Istanbul time: daily at midnight, weekly on Monday at midnight and monthly on the first day at midnight. By default, admission closes 60 minutes before the next period: daily entries close at 23:00, and the new game opens at midnight. Weekly and monthly cutoffs apply on their final day. The cutoff instant is excluded from admission. Periods missed while the server was offline do not create retroactive rounds or purchases.

## 2. Admin settings and game selection

Open Period battles in the provider console. The master switch and daily, weekly and monthly switches are independent. Each period has its own entry fee, game/provider pool, bonus type, commission, top-three prize percentages and admission cutoff. `entryCloseMinutes` accepts 1–720 minutes and defaults to 60. It is also frozen when the period opens. Defaults are 50% / 30% / 20%, with bonus winnings as the pool source. Percentages must total 100%.

One eligible game is selected when a period opens. The game, bonus type, amount, pool model and prize percentages remain fixed for that period. Settings changes apply to the next period. Disabling the master or period switch stops new entries; it does not cancel running rounds or owed awards.

An explicitly selected game must support the configured amount and bonus type. Provider pools include only games that satisfy these conditions. Each player's site game and amount restrictions also apply. The platform ceiling does not raise an upstream provider's actual purchase or demo credit limit.

## 3. Score, pool and payment

Admission reserves the fee once through the existing wallet flow. The bonus round runs through the existing game adapter; verified bonus winnings become the leaderboard score. Bonus winnings are not separately paid to the player when the round ends. The reservation stays locked until the period is settled.

The pool has two available models:

- Bonus winnings: the sum of verified bonus winnings from valid completed rounds.
- Entry fees: the sum of fees from valid completed entries. Bonus winnings determine ranking only; they are not paid separately or added to this pool.

The module-specific commission is deducted from the selected pool. The net pool is distributed to the top three ranks. With fewer than three participants, the available rank weights are normalized to 100%. Tied scores share the combined awards for the ranks they occupy, including a tie extending past third place. Indivisible minor units follow stable entry-ID order; the total is conserved. No valid entries means zero payout.

New purchases stop at the admission cutoff. Distribution begins once the final rounds are verified; it does not need to wait until midnight. If a round is running, under review or waiting for wallet reservation reconciliation, the period waits in its verification state. If a final round crosses midnight, its old period waits while the next period opens independently. No estimated score or automatic second purchase is created. A cancelled and refunded entry is excluded from the pool and leaderboard; it does not reopen admission for that player in the same period.

Awards enter the durable payment queue and existing site-wallet reconciliation. A timeout is not a new payment; the same operation identity is retried. Signed seamless-wallet callbacks include `battle.competition` with period ID, kind, status, admission cutoff (`entryClosesAt`), next-period boundary (`endAt`), pool model, rank and award. Before settlement the outcome is `pending`; after settlement it is `win` for an awarded entry or `loss` otherwise, and `refund` for cancellation/refund. The UI separately indicates payments awaiting acknowledgment.

## 4. Iframe and module integration

The existing identity and wallet integration is reused; no additional player account is created. When creating a normal launch ticket on your backend, you may use this route:

```json
{
  "externalId": "member-123",
  "origin": "https://operator.example",
  "route": "competitions"
}
```

These fields belong to the existing `/provider/v1/launch` request body. Keep the API key exclusively on the operator backend. Iframes use the current provider UI. File-based module installations need the updated client package. Players enter through the provider's Period battles page and receive awards in their own site wallet.

## 5. UI APIs and permissions

The following endpoints use the player UI session; an operator server API key does not substitute for that session.

```http
GET /play/:slug/api/competitions
GET /play/:slug/api/competitions/history?kind=daily&date=2026-10-27
POST /play/:slug/api/competitions
Content-Type: application/json
```

```json
{"periodId":"daily-<server-issued-period-start>"}
```

Read `periodId` from the list response. Do not submit an amount, score, award or bonus with admission. The server obtains those settings from the period record. Repeating a valid entry returns the same `roomId`. Streaming and replay use the existing room endpoints. The list returns the latest 10 periods of each kind (up to 30), the top 100 scores per period and the signed-in player's own result.

For history, `kind` is daily/weekly/monthly and `date` uses YYYY-MM-DD in Istanbul time. Today and the previous 30 days are accepted; invalid or out-of-range dates return 400. The response includes the period containing that date, its top 100 scores, the signed-in player’s result and period summaries covering the last 30 days. A date with no record returns `period: null`. Summaries preview three entries; the full board includes ties within its first 100 scores. A new period starts with zero participants in a separate record; previous scores, awards and transaction records are not deleted. The 30-day UI window is not a data deletion policy.

Admin endpoints use a separate provider-panel session. Reading requires `settings.read`; updates require `settings.manage` and the existing CSRF header.

```http
GET /control/api/competitions
PATCH /control/api/competitions
```

The PATCH body is the full `settings` object with its current `revision`. Concurrent writes with a stale revision return 409. Amounts use integer minor units and rates use basis points: 500000 = TRY 5,000; 500 = 5%. The panel edits these as TRY and percentages. Settings changes are recorded in the audit log.

## 6. Before enabling production

- Configure the game pool and supported bonuses while the module is disabled.
- Verify allowed amounts, game access and wallet model for each site.
- Test admission, request retries, round results, period closing, ties and wallet retries.
- Separately observe the actual provider purchase, result verification and stream. Controlled provider responses in unit tests are not real-game validation.
- Show the period fee, commission and prize distribution to players. Do not present pending wallet deliveries as completed payments.


## 7. Site entry and prize report

The **Period battles** page in the operator console shows only players belonging to the signed-in site. Choose daily, weekly or monthly battles and a historical period. Staff need all three permissions: `rooms.read`, `players.read` and `finance.read`. Ranks are global, but no other site's player identities, usernames, rooms or financial rows are returned.

```http
GET /operator-api/competitions?kind=daily&limit=50&offset=0
```

This endpoint uses the operator panel cookie session. A server API key does not replace that session. To read the same report from your backend, use your site-bound key with `read` scope:

```http
GET /provider/v1/competitions?kind=daily&limit=50&offset=0
Authorization: Bearer <SERVER_API_KEY>
```

```javascript
const report = await client.competitions({ kind: 'daily', limit: 50 });
const previous = report.periods[1];
if (previous) {
  const archive = await client.competitions({
    kind: 'daily', periodId: previous.id, offset: 0, limit: 50
  });
}
```

- `kind`: `daily`, `weekly` or `monthly`; default `daily`.
- `periodId`: select from `periods`; omitted means the latest period of that kind. A missing period or mismatched kind returns 404.
- `limit`: 1–100, default 50. `offset`: 0–100000000, default 0. Unknown or repeated query fields return 400; do not send `tenantId`.
- `periods`: the latest 100 period headers of the selected kind. An older known valid period ID can also be read; records are not deleted.
- `period`: the game, bonus, entry fee, cutoff and distribution settings frozen at opening; `null` when no period has opened.
- `summary`: totals for this site's entries throughout the selected period, independent of pagination.
- `entries`: only this site's paginated entries. `publicId` is the arena identity, `externalId` is the site's member identity, and `siteUsername` is the username supplied by the site. Missing private identity fields may be `null`.
- `rank`: rank across all sites. Provisional before settlement; tied scores share a rank. Cancelled or unfinished entries have `null` rank.
- `pagination.total` and `hasMore`: continuation details; keep the same `periodId` on subsequent pages.

All amounts are integer TRY minor units. `entryVolume` is the total cost of accepted entries, including cancelled entries. `refunds` is acknowledged refunds and `pendingRefunds` is refunds awaiting confirmation. `totalScores` sums valid verified bonus scores. `awardedPrizes` is finalized awards, `paidPrizes` is acknowledged wallet delivery and `pendingPrizes` is the remaining unacknowledged amount. Prizes and entry volume are not operator revenue; `operatorShare` is the site's commission share. Do not display pending awards or refunds as paid.

Entry `status` is `playing`, `scored` or `void`. `paymentStatus` is `awaiting-period` before distribution, `pending` during reconciliation, `complete` once settled, and `refund-pending` / `refunded` for refunds. Winner count means site entries with a positive award; ties may produce more than three award recipients.

## 8. Event history and package updates

`/provider/v1/events` and `/provider/v1/webhooks/events` add `competition` context to period-room movements: `periodId`, `kind`, `entryId`, `entryStatus`, `periodStatus`, `gameId`, `gameName`, `buyInMinor`, `scoreMinor`, `prizeMinor`, `rank`. This is **current context at read time**, not a historical snapshot of the event. Financial event `id`, `cursor`, `amountMinor` and `balanceAfter` stay immutable. Never credit a wallet again from `scoreMinor` or `prizeMinor`. In seamless mode only signed callbacks with durable transaction identities authorize money movements; pulled events are not new payment instructions.

Iframes automatically display the provider's current UI; existing identity and wallet integrations remain intact. Module installations must deploy the updated `slot-battles-client.zip` contents together. For server reporting, update `slot-battles-sdk.mjs` and the OpenAPI schema. The complete integration ZIP contains both period guides, the updated SDK and client. The operator's Postman collection includes a period-report GET request. Packages contain no API keys or site player records.
