237 lines
14 KiB
Markdown
237 lines
14 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.1 or later**
|
||
before installing the public-endpoint bank scripts. This version settles both
|
||
public and legacy controller-derived journals before removing runtime storage.
|
||
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` 2.0.0, common 1.0.0+ and telemetry 1.0.0+.
|
||
For existing servers changing endpoints, use the rebuild procedure below.
|
||
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 only `tws_bank_token` in
|
||
that private file; it no longer generates `tws_bank_url`.
|
||
|
||
Both game scripts always send bank requests to
|
||
`https://ec.thewrightserver.net/api/bank`, even if an old `tws_bank_url` remains
|
||
in configuration. The host agent's enrolled HTTPS controller URL is separate
|
||
and continues to carry agent traffic, including authenticated bank settlement.
|
||
It may differ from the public endpoint reachable by game containers.
|
||
|
||
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.
|
||
|
||
Drift scans exclude the exact generated configuration path for both games.
|
||
A maintenance rescan resolves old bank-only incidents and retains other file
|
||
changes in mixed incidents. Private-file campaign plans saved before this fix
|
||
are rejected at activation; cancel them and rescan rather than retrying the old
|
||
plan. Upgrade host agents to **1.9.3 or later** before resuming drift recovery
|
||
so stages already saved on a host also reject private-file reads and changes.
|
||
Legacy private bank-file campaign diffs and their blob downloads are blocked;
|
||
this does not erase previously captured bytes from databases or backups.
|
||
|
||
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.
|
||
|
||
### Upgrading from a controller-derived game endpoint
|
||
|
||
Update agents to `1.9.1+` first. If an existing server's `tws_bank_url` differs
|
||
from `https://ec.thewrightserver.net/api/bank`, rebuild that server through EC
|
||
with bank scripts `2.0.0`. The agent stops the old game, settles its pending
|
||
commit/cancel decisions with their original request IDs, then starts the
|
||
replacement and removes old storage. The server identity and instance credential
|
||
are preserved. Lost acknowledgments can replay the same decisions; EC's retained
|
||
request IDs prevent duplicate ledger changes. An outage or invalid journal
|
||
retains storage and blocks replacement until recovery succeeds.
|
||
|
||
Do not deploy the new scripts with only a map restart when the endpoint changes:
|
||
native journals are keyed by server and endpoint, and the new scripts cannot load
|
||
the old scope. Do not rename, rewrite or delete pending journals. Keep the
|
||
agent's enrolled controller URL unchanged until legacy journals drain; the
|
||
agent accepts only the fixed public endpoint and the legacy `/api/bank` URL
|
||
derived from that enrolled controller. Other legacy URLs require recovery with
|
||
their original configuration before switching. If the old URL already matches
|
||
the exact public endpoint, existing journals retain their identity and a map
|
||
restart is sufficient for the script upgrade. No new native plugin build is
|
||
required beyond `1.1.0`.
|
||
|
||
## 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.1+
|
||
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 the public game-bank endpoint or that host's legacy
|
||
controller-derived bank endpoint; the agent never follows a journal URL.
|
||
Corruption, an unrecognized endpoint, 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
|
||
|
||
The **Players → Bank** page shows fleet-wide balances, funded accounts, completed
|
||
transfers in the last 24 hours and pending transfers. Accounts can be searched by
|
||
player name or GUID, sorted by balance or recent completed activity, and filtered
|
||
to funded accounts, accounts at or above the configured limit, or pending transfers.
|
||
Balances always remain shared across both games.
|
||
|
||
Transactions can be filtered by player, game, server, direction and inclusive UTC
|
||
dates. Pending transfers show oldest first, with an attention indicator after five
|
||
minutes and details explaining the reservation. This indicator does not expire or
|
||
cancel a transfer. Both views retain historical server identities and match IDs
|
||
after retention; unavailable matches have no live link.
|
||
|
||
Selecting a player opens a drawer with their balance, available points, pending
|
||
transfer and paginated history. Filters and pagination are retained in the URL,
|
||
including when following a match or player-profile link. The page refreshes every
|
||
30 seconds while visible. Summary counts always cover the whole fleet. Bank is
|
||
read-only for all browser roles; transfers continue to originate in game.
|
||
|
||
Player profiles show the shared balance, pending status and three newest ledger
|
||
entries, with a **View bank history** link to the drawer. Entries include 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 tests/unit/bank-overview.test.ts tests/client/bank-page.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.
|
||
For the endpoint upgrade, also test a different enrolled controller address and
|
||
a stale `tws_bank_url`, `.b` with an empty balance, `.w all` with no funds, and
|
||
rebuild/removal with both legacy and public journals. Drop a settlement reply,
|
||
restart the agent and verify the original IDs settle without duplicate ledger
|
||
changes before the replacement starts or runtime storage is removed.
|