Add persistent cross-game player bank (Refs #146)
This commit is contained in:
@@ -25,6 +25,7 @@ and launch your first server.
|
||||
- [Beta test plans](guides/beta-test-plans.md) — game reporters, test requirements, coverage, and outcomes.
|
||||
- [Plugin releases](guides/plugin-releases.md) — GitHub release discovery and imports.
|
||||
- [Metrics](guides/metrics.md) — utilization, capacity pressure, and hosting experiments.
|
||||
- [Player bank](guides/bank.md) — shared balances, per-server credentials, transfers and recovery.
|
||||
- [Leaderboard and matches](guides/leaderboard.md) — player statistics, match history, and the read API.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
# Player bank
|
||||
|
||||
The bank stores one points balance per exact GUID across T5 and T6. Players use
|
||||
`.b`, `.d <amount|all>` and `.w <amount|all>` with the TWS bank scripts. Deposits
|
||||
move spendable match points into the bank; withdrawals return bank points to
|
||||
the current match. Neither changes earned `score_total`. Bank-assisted games
|
||||
still count toward the existing leaderboards; the ledger records their match
|
||||
IDs. This release does not introduce separate bank-assisted rankings.
|
||||
|
||||
## Setup and rollout
|
||||
|
||||
1. Back up the controller database. Deploy Electric Cherry first and restart
|
||||
the controller. Migration 20 creates accounts, transfer requests and a ledger.
|
||||
2. Generate a **different** secret for each registered game server, for example
|
||||
`ec_bank_` followed by `openssl rand -hex 32`. Configure `EC_BANK_TOKENS` as a
|
||||
JSON object mapping exact server names to these secrets. Docker Compose
|
||||
forwards this environment variable. An example shape with placeholders is
|
||||
`{"t5_zombie_theater_1":"ec_bank_<random-secret>"}`.
|
||||
3. Set `EC_BANK_MAX_BALANCE` (default 250000, range 1–1000000). Invalid configuration
|
||||
refuses new deposits. A limit reduction does not confiscate existing balances
|
||||
or invalidate already prepared deposits; withdrawals remain available.
|
||||
4. Build/install TWS plugins `t5-plugin`/`t6-plugin` 1.1.0 or later and restart
|
||||
the **whole game-server process**. Install `tws_t5_bank.gsc` or
|
||||
`tws_t6_bank.gsc` 1.0.0, the game's common module (1.0.0+) and telemetry
|
||||
reporter (1.0.0+). Restart the map after installing scripts/configuration.
|
||||
5. In each server's private configuration, set `tws_server_id` to its registered
|
||||
name and `tws_bank_token` to that server's secret. The optional `tws_bank_url`
|
||||
defaults to `https://ec.thewrightserver.net/api/bank`; overrides must use
|
||||
HTTPS. Keep these settings out of shared scripts and public config downloads.
|
||||
|
||||
Bank tokens must match `ec_bank_` followed by 32–128 URL-safe letters, digits,
|
||||
underscores or hyphens. Missing, malformed, duplicate, or telemetry-reused tokens
|
||||
disable authentication. `EC_TELEMETRY_TOKEN`, host tokens and browser sessions
|
||||
cannot authorize bank requests. The credential determines the server, and a
|
||||
different `server` in the body is refused. Rotate a token by updating both the
|
||||
controller mapping and that server's private configuration, then restart them.
|
||||
Keep the same server name and bank URL so existing decisions can finish.
|
||||
|
||||
## Machine API
|
||||
|
||||
All routes below are `POST /api/bank/<action>`, with `Content-Type:
|
||||
application/json` and `Authorization: Bearer <server-bank-token>`. Responses
|
||||
are never cached. Bodies are capped at 4096 bytes. GUIDs and request IDs stay
|
||||
strings; never convert them to numbers.
|
||||
|
||||
| Action | Body | Meaning |
|
||||
| ---------- | ------------------------- | -------------------------------------------------------------------- |
|
||||
| `balance` | `server`, `guid` | Read the shared balance and currently available points |
|
||||
| `deposit` | Transfer fields below | Reserve deposit capacity without crediting the account yet |
|
||||
| `withdraw` | Transfer fields below | Reserve bank funds without debiting the account yet |
|
||||
| `commit` | Identical transfer fields | Apply a prepared transfer once and append its ledger entry |
|
||||
| `cancel` | Identical transfer fields | Release a reservation; also prevents a delayed prepare from applying |
|
||||
|
||||
Transfer example:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": "t5_zombie_theater_1",
|
||||
"guid": "18446744073709551615",
|
||||
"match": "reporter-match-key",
|
||||
"request_id": "unique-process-token-17",
|
||||
"operation": "deposit",
|
||||
"amount": 2000,
|
||||
"capacity": 995000
|
||||
}
|
||||
```
|
||||
|
||||
`match` is the reporter's telemetry match key, **not** Electric Cherry's numeric
|
||||
match ID. `request_id` is unique for the server and at most 64 characters.
|
||||
`amount` is a positive integer up to 1000000; withdrawal also accepts `"all"`.
|
||||
The script resolves deposit `all` to its captured spendable points. `capacity`
|
||||
is the current room below the game's bank withdrawal cap of 1000000 points.
|
||||
An `all` withdrawal that exceeds this room is refused, rather than truncated.
|
||||
|
||||
Preparation requires the GUID to be present in the server's latest active match,
|
||||
with the exact telemetry match key. Late joiners may need to wait for the next
|
||||
30-second heartbeat. Unknown/destroyed servers, absent players and old matches
|
||||
cannot prepare transfers. Settlement of an existing decision remains possible
|
||||
after match completion, retention or server deletion.
|
||||
|
||||
Successful application responses include `ok`, `state`, `code`, `request_id`,
|
||||
`amount` and `balance`. States are `balance`, `prepared`, `committed`, `cancelled`
|
||||
or `rejected`. For balance reads, `amount` is the available balance. A business
|
||||
rejection uses HTTP 200 with `ok: false`, `state: "rejected"` and a code such as
|
||||
`insufficient_balance`, `empty`, `bank_limit`, `points_limit`, `not_in_match` or
|
||||
`pending`. It never changes points. Authentication failures use 401/403,
|
||||
malformed input 400/413/415, identity conflicts 409, and unavailable service 503.
|
||||
|
||||
Repeated requests return the saved result/state without applying a second
|
||||
change. Settled IDs never reopen; a committed result retains its original
|
||||
resulting balance even if later transfers change the account. Reusing an ID
|
||||
with different transfer fields returns 409. A GUID has at most one prepared
|
||||
transfer across all servers. SQLite transactions serialize checks, account
|
||||
updates and ledger insertion, so concurrent requests cannot overspend funds or
|
||||
deposit past the configured maximum.
|
||||
|
||||
## Lost responses and recovery
|
||||
|
||||
The game saves a cancellation decision **before** sending a prepare request.
|
||||
Only a validated prepared reply lets it recheck the player's life, current
|
||||
spendable points and withdrawal room. It then records a commit decision and
|
||||
updates spendable points without yielding. The sender retries settlement with
|
||||
the original identity, at delays of 1, 2, 4, 8, 16, then 30 seconds. Invalid,
|
||||
truncated or uncorrelated replies never authorize points changes.
|
||||
|
||||
A failed/uncertain prepare leaves game points unchanged and queues cancellation.
|
||||
Cancellation can arrive first: its tombstone blocks the delayed prepare. Death,
|
||||
respawn, disconnect or match end before application also cancels. After a commit
|
||||
decision, retries only finish the ledger; they never change game points again.
|
||||
These points now belong to the current match, so an applied withdrawal is not
|
||||
automatically refunded if that match ends or its server crashes.
|
||||
|
||||
Keep `fs_homepath/tws-bank/t5/` or `t6/` on persistent writable storage with a
|
||||
single process per server/endpoint. Journals contain transfer data, not secrets.
|
||||
On a map/process restart, saved decisions settle before new bank commands.
|
||||
Unreadable storage disables the bank; failed writes prevent points changes.
|
||||
Pending decisions remain until acknowledged, even on credential errors. Bank
|
||||
holds do not expire automatically, because expiring a saved commit can lose a
|
||||
deposit or create a free withdrawal. Restore connectivity/configuration and the
|
||||
original journal to resolve holds; never delete a pending journal to unblock
|
||||
the bank. If storage is permanently lost, reconcile against the retained
|
||||
request and ledger records before any manual repair.
|
||||
|
||||
The journal provides recovery from map/process restart. It cannot make live
|
||||
game memory and SQLite one atomic transaction or preserve match points after a
|
||||
machine/storage failure. Back up persistent storage and verify crash behavior
|
||||
in the actual Windows/Plutonium deployment before rollout.
|
||||
|
||||
## History and validation
|
||||
|
||||
Player profiles show the shared balance, pending status and 20 newest ledger
|
||||
entries, including signed amounts, resulting balances, server, time and match.
|
||||
Accounts, request IDs and ledger entries have no cascading match/server foreign
|
||||
keys and are not deleted by match retention or server deletion. Retained IDs
|
||||
also keep old retries idempotent. Expired match links become plain historical
|
||||
IDs. Include these tables in normal database backups.
|
||||
|
||||
Run `npx vitest run tests/unit/bank.test.ts`, then the usual lint, type checks,
|
||||
tests and build. TWS supplies `node tools/check_bank.mjs` and
|
||||
`python3 tools/test_plugin_bank.py`. Live validation must cover both games:
|
||||
deposit/withdraw amounts and `all`, insufficient funds and caps, joining before
|
||||
telemetry, `.help`, score HUD updates, death/respawn/disconnect during requests,
|
||||
lost prepare/commit replies, controller outage, disk failures and map/process
|
||||
restart with pending work. Confirm earned score and leaderboard behavior, and
|
||||
verify that another server's credential and the telemetry credential fail.
|
||||
Reference in New Issue
Block a user