aboutsummaryrefslogtreecommitdiff
path: root/building_guide/concurrency.md
diff options
context:
space:
mode:
Diffstat (limited to 'building_guide/concurrency.md')
-rw-r--r--building_guide/concurrency.md36
1 files changed, 35 insertions, 1 deletions
diff --git a/building_guide/concurrency.md b/building_guide/concurrency.md
index 984a61d..2fa4558 100644
--- a/building_guide/concurrency.md
+++ b/building_guide/concurrency.md
@@ -37,6 +37,17 @@ Rules:
per-message write deadlines. A full queue disconnects only that client.
- Saves run on the owner and serialize a value snapshot; concurrent
read-modify-write cycles of the same files cannot happen.
+- Password work is the one deliberate exception: Argon2id verification and
+ hashing (~64 MiB, tens of milliseconds) run on the CALLER's connection
+ goroutine in `HandleSessionOnOwner` (`StatePassword` and
+ `StateNewAccount` are intercepted before any owner task is submitted).
+ A burst of logins therefore costs memory and CPU on connection
+ goroutines, never a stall of the owner's command/tick queue. The owner
+ task re-checks the stored hash against the one that was verified so a
+ password change mid-login cannot authorize a stale attempt, and
+ `player.hashLimiter` caps concurrent Argon2id computations at 4.
+ All resulting state transitions (echo, session state, account saves)
+ still happen on the owner.
Unit tests construct Games without an owner; such Games run submitted tasks
inline on the caller's goroutine (legacy behavior), which is fine for
@@ -126,6 +137,26 @@ an insufficient-funds message). A failed payout keeps its record open (the
reported win is never announced as paid) and startup recovery re-pays the
owed settlement.
+If the JOURNAL ITSELF cannot be appended (persistent write failure, e.g. a
+deleted or unwritable `data/economy`), appends are retried and then the
+wallet degrades safely:
+
+- A **wager** is never charged without a journal record — the bet is
+ rejected with the persistence message (unchanged).
+- A **payout** falls back to paying through the character file alone (the
+ save is the commit point), so the settlement is real, announced, and
+ durable even with no journal record. `ResolveStakes` then records the
+ unresolved stake IDs in the character file (`player.Player
+ .ResolvedStakes`) as durable resolution evidence, and recovery settles
+ those stakes without refunding them (no double pay, and a settlement's
+ chip stake is not returned in kind on top of credits that already
+ returned it). If the character save fails too, no evidence exists and
+ the open wager record drives the ordinary refund-on-lost-state recovery.
+- A journal that cannot be fully READ (oversized or unreadable line)
+ aborts recovery and compaction entirely: records are preserved verbatim
+ for operator inspection instead of compensating over a partial fold or
+ truncating the file.
+
At startup (`game.New`), open records are compensated in two phases:
1. Open **payout** records (append order, evolving balance): file at the
@@ -145,7 +176,10 @@ At startup (`game.New`), open records are compensated in two phases:
Currency arithmetic is checked (`internal/casino/credits.go`): explicit
`MaxCredits` cap, bounded `DefaultMaxBet` default (the explicit unlimited
sentinel is still honored), floor conversion for payout multipliers, and
-overflow rejected instead of wrapping.
+overflow rejected instead of wrapping. The cap is shared by the wallet:
+bank withdrawals are rejected when they would push the on-hand balance
+past it, and gameplay credit grants (shop sales, pickups, activity
+payouts) clamp at the cap and log rather than wrap.
## Account lifecycle transactions