173 lines
10 KiB
Markdown
173 lines
10 KiB
Markdown
# 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. Update every host agent to **1.9.0 or later**
|
||
before enabling the bank. This version settles game journals before removing
|
||
runtime storage; older agents do not. Package and offline-sign the agent
|
||
release using the [host update workflow](native-hosts.md).
|
||
2. Deploy Electric Cherry and restart the controller. Migration 20 creates
|
||
accounts, requests and the ledger; migration 21 adds instance credentials and
|
||
binds existing requests to their currently active native instance, if any.
|
||
3. Set `EC_BANK_MAX_BALANCE` (default 250000, range 1–1000000). Invalid configuration
|
||
refuses new deposits. Lowering the limit preserves existing balances and
|
||
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, common 1.0.0+ and telemetry 1.0.0+.
|
||
5. Create or rebuild the servers through EC. Launch preparation generates a
|
||
cryptographically random `ec_bank_` credential for each immutable instance,
|
||
stores it in the private controller database, and injects it through
|
||
`configs/electric_cherry_bank.cfg`. The launch command executes this private
|
||
file and sets `tws_server_id`. The host agent writes the private file and derives `tws_bank_url` from its
|
||
enrolled controller URL. Use an HTTPS controller URL.
|
||
|
||
There is **no `EC_BANK_TOKENS` environment map** and no per-server secret setup.
|
||
The private configuration is sent only in that host's authenticated operation
|
||
payload. It is excluded from shared recipes, promoted files and blob downloads.
|
||
Do not upload `electric_cherry_bank.cfg`: its filename is reserved. Protect the
|
||
controller database, host operation journal and per-instance runtime directory,
|
||
which contain private credentials.
|
||
|
||
Retries, rebuilds and host moves of the same instance reuse its credential.
|
||
Retiring the instance immediately revokes authentication. Creating another
|
||
server with the same display name produces a new instance ID and secret; it
|
||
cannot finish the old instance's requests. Accounts remain attached to player
|
||
GUIDs, so balances survive the servers. Telemetry tokens and browser sessions
|
||
cannot authorize bank requests. Host credentials can only settle decisions
|
||
through the separate, operation-scoped teardown endpoint.
|
||
|
||
For an upgrade from the initial static-token implementation, drain outstanding
|
||
transfers while the old controller/configuration still runs, then deploy and
|
||
rebuild. Remove the obsolete environment map and any manually supplied token.
|
||
Do not replace identities or discard pending journals to force an upgrade.
|
||
|
||
## 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 or retention. Host teardown can finish saved decisions
|
||
after retirement; the retired game credential itself no longer authenticates.
|
||
|
||
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 `tws-bank/t5/` or `tws-bank/t6/`, relative to the game process working
|
||
directory, 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.
|
||
|
||
During removal, rebuild, host migration or launch cancellation, agent 1.9.0+
|
||
stops the game and drains its journals **before** deleting runtime files or
|
||
starting a replacement. It uses its host credential at
|
||
`POST /api/agent/bank/settle`; EC checks the assigned host, immutable instance,
|
||
unfinished teardown operation, server and original request identity. This
|
||
endpoint permits only commit/cancel, never new deposits or withdrawals. Journal
|
||
URLs must match that host's configured controller; the agent never follows a
|
||
journal URL. Corruption, a mismatched controller, failed acknowledgment or an
|
||
outage retains storage and leaves the operation unresolved for retry. An
|
||
interrupted rebuild uses the existing rebuild recovery workflow to retry with
|
||
a fresh operation after settlement. Do not manually erase its old directory.
|
||
|
||
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.
|