aboutsummaryrefslogtreecommitdiff
path: root/internal/game/readiness_regression_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 21:36:36 -0400
committerhistoria <[not public]>2026-09-18 21:36:36 -0400
commita8a67482bbff8297a96adff65d2fd7029ed998cc (patch)
treee84f8ba636c8f2edb4d2ff8b45d7cff7298fa7cc /internal/game/readiness_regression_test.go
parentff04d26ce90d6b411a04a2fb59f7c24ceefb161f (diff)
downloadthehouseoficarus-a8a67482bbff8297a96adff65d2fd7029ed998cc.tar.gz
refactor: replace economy journal with single-file pending-wager wallet
Diffstat (limited to 'internal/game/readiness_regression_test.go')
-rw-r--r--internal/game/readiness_regression_test.go216
1 files changed, 4 insertions, 212 deletions
diff --git a/internal/game/readiness_regression_test.go b/internal/game/readiness_regression_test.go
index 41170b7..442bb1f 100644
--- a/internal/game/readiness_regression_test.go
+++ b/internal/game/readiness_regression_test.go
@@ -1,18 +1,15 @@
package game
-// Readiness regressions for the account-rename, disconnect, and durable-money
-// paths reviewed before world/casino expansion: a transport disconnect must
-// be race-free, renames must not break ownership or strand owed payouts,
-// reserved-loot pickups must never destroy items, and recovery failure paths
-// must retry or surface instead of silently dropping money.
+// Readiness regressions for the account-rename and disconnect paths reviewed
+// before world/casino expansion: a transport disconnect must be race-free,
+// renames must not break ownership, and reserved-loot pickups must never
+// destroy items.
import (
- "os"
"path/filepath"
"testing"
"thehouseoficarus/internal/net"
- "thehouseoficarus/internal/player"
)
// TestDisconnectRaceIsFreeAndNeverLost drives DisconnectTick (owner) while a
@@ -72,40 +69,6 @@ func TestReservedPickupDoesNotDestroyItems(t *testing.T) {
})
}
-// TestOwedPayoutSurvivesCharacterRename: economy records resolve their
-// subject by the character's STABLE ID, so renaming the character between
-// the crash and recovery still delivers the owed payout.
-func TestOwedPayoutSurvivesCharacterRename(t *testing.T) {
- h := newHarness(t)
- h.seedAccount("acct")
- p := h.seedCharacter("alice", "acct")
- h.g.RunOnOwner(func() {
- p.ID = "stable-char-id" // production characters carry a stable ID
- p.Credits = 90
- if err := h.g.AccountStore.SaveCharacter(p); err != nil {
- t.Error(err)
- return
- }
- if _, err := h.g.Economy.start("payout", p, 0, 20, 90, 110, 0, 0); err != nil {
- t.Error(err)
- return
- }
- if err := h.g.renameCharacterTxn("acct", "alice", "alicia"); err != nil {
- t.Error(err)
- return
- }
- h.g.recoverEconomy()
- loaded, err := h.g.AccountStore.LoadCharacter("alicia")
- if err != nil {
- t.Error(err)
- return
- }
- if loaded.Credits != 110 {
- t.Errorf("renamed character never receives owed payout: credits=%d, want 110", loaded.Credits)
- }
- })
-}
-
// TestAccountRenamePreservesAccessAndOwnership: renaming an account
// rewrites the owned character files' owner field in the same transaction
// and republishes stale sessions (still carrying the old cached name) so
@@ -127,174 +90,3 @@ func TestAccountRenamePreservesAccessAndOwnership(t *testing.T) {
}
})
}
-
-// TestSettledSurrenderDoesNotRefundChipStake: a settlement payout — even a
-// partial one like a surrendered hand paying half a chip stake — proves the
-// round resolved, so recovery must not ALSO return the chip stake in kind.
-func TestSettledSurrenderDoesNotRefundChipStake(t *testing.T) {
- h := newHarness(t)
- h.seedAccount("alice")
- p := h.seedCharacter("alice", "alice")
- h.g.RunOnOwner(func() {
- p.Credits = 100
- p.SetInvSlot(0, &player.InventorySlot{ItemID: "chips", Quantity: 10})
- w := casinoWallet{g: h.g, p: p}
- if _, err := w.Wager(10); err != nil {
- t.Error(err)
- return
- }
- // Surrender settles for half the stake; the crash lands before the
- // resolution marker.
- if err := w.PayCredits(5); err != nil {
- t.Error(err)
- return
- }
- h.g.recoverEconomy()
- loaded, err := h.g.AccountStore.LoadCharacter("alice")
- if err != nil {
- t.Error(err)
- return
- }
- if loaded.Credits != 105 || countChips(loaded) != 0 {
- t.Errorf("settled surrender recovered %d credits / %d chips; want 105 / 0", loaded.Credits, countChips(loaded))
- }
- })
-}
-
-// TestRecoverySaveFailureRetriesOnNextStartup: a TRANSIENT save failure
-// during recovery must not re-anchor the owed record to unreadable
-// checkpoints — the record keeps its original checkpoints and a later
-// recovery (after the disk is fixed) re-applies the owed payout.
-func TestRecoverySaveFailureRetriesOnNextStartup(t *testing.T) {
- h := newHarness(t)
- h.seedAccount("alice")
- p := h.seedCharacter("alice", "alice")
- charDir := filepath.Join(h.dir, "players", "characters")
- t.Cleanup(func() { os.Chmod(charDir, 0755) })
- h.g.RunOnOwner(func() {
- p.Credits = 90
- if err := h.g.AccountStore.SaveCharacter(p); err != nil {
- t.Error(err)
- return
- }
- if _, err := h.g.Economy.start("payout", p, 0, 20, 90, 110, 0, 0); err != nil {
- t.Error(err)
- return
- }
- if err := os.Chmod(charDir, 0500); err != nil {
- t.Error(err)
- return
- }
- h.g.recoverEconomy() // every save fails
- if err := os.Chmod(charDir, 0755); err != nil {
- t.Error(err)
- return
- }
- h.g.recoverEconomy() // retry with a healthy disk
- loaded, err := h.g.AccountStore.LoadCharacter("alice")
- if err != nil {
- t.Error(err)
- return
- }
- if loaded.Credits != 110 {
- t.Errorf("after fixing the disk and retrying: %d credits, want 110; open=%+v", loaded.Credits, h.g.Economy.mustLoadOpen(t))
- }
- })
-}
-
-// TestResolvedStakeEvidenceSurvivesJournalFailure: a resolution marker
-// that cannot be journaled (persistent append failure) leaves the stake
-// OPEN — never silently forgotten — and records the resolution in the
-// character file as durable evidence. Recovery then settles the stake
-// WITHOUT refunding it: the round genuinely resolved, and the file is the
-// proof. When the file save also fails, no evidence exists and recovery
-// applies the documented refund-on-lost-state policy instead.
-func TestResolvedStakeEvidenceSurvivesJournalFailure(t *testing.T) {
- h := newHarness(t)
- h.seedAccount("alice")
- p := h.seedCharacter("alice", "alice")
- h.g.RunOnOwner(func() {
- p.Credits = 100
- w := casinoWallet{g: h.g, p: p}
- if _, err := w.Wager(10); err != nil {
- t.Error(err)
- return
- }
- path := h.g.Economy.path
- h.g.Economy.path = h.dir // persistent append failure
- w.ResolveStakes()
- h.g.Economy.path = path
- if open := h.g.Economy.openStakes(economyKey(p)); len(open) != 1 {
- t.Errorf("unresolvable stake was silently dropped: open=%d", len(open))
- }
- // The resolution is recorded durably in the character file.
- loaded, err := h.g.AccountStore.LoadCharacter("alice")
- if err != nil {
- t.Error(err)
- return
- }
- if len(loaded.ResolvedStakes) != 1 {
- t.Errorf("resolved-stake evidence not recorded: %+v", loaded.ResolvedStakes)
- return
- }
- h.g.recoverEconomy()
- loaded, err = h.g.AccountStore.LoadCharacter("alice")
- if err != nil {
- t.Error(err)
- return
- }
- if loaded.Credits != 90 {
- t.Errorf("after recovery: credits=%d, want 90 (round resolved; evidence prevents a refund)", loaded.Credits)
- }
- if open := h.g.Economy.mustLoadOpen(t); len(open) != 0 {
- t.Errorf("records left open after recovery: %+v", open)
- }
- if len(loaded.ResolvedStakes) != 0 {
- t.Errorf("stale resolved-stake evidence not pruned: %+v", loaded.ResolvedStakes)
- }
- })
-}
-
-// TestUnjournalablePayoutPaidThroughCharacterFile: a payout that cannot be
-// journaled (persistent append failure) is still made durable through the
-// character file — the save is the commit point, so the win is real and
-// announced. The resolved stake is recorded as file evidence so recovery
-// neither refunds the stake (double pay) nor drops the round.
-func TestUnjournalablePayoutPaidThroughCharacterFile(t *testing.T) {
- h := newHarness(t)
- h.seedAccount("alice")
- p := h.seedCharacter("alice", "alice")
- h.g.RunOnOwner(func() {
- p.Credits = 100
- w := casinoWallet{g: h.g, p: p}
- if _, err := w.Wager(10); err != nil {
- t.Error(err)
- return
- }
- path := h.g.Economy.path
- h.g.Economy.path = h.dir
- err := w.PayCredits(20)
- h.g.Economy.path = path
- if err != nil {
- t.Errorf("character-file fallback failed: %v", err)
- return
- }
- if p.Credits != 110 {
- t.Errorf("fallback payout did not land: credits=%d, want 110", p.Credits)
- }
- // As the games do after a successful settlement: resolve the stake.
- w.ResolveStakes()
- h.g.recoverEconomy()
- loaded, err := h.g.AccountStore.LoadCharacter("alice")
- if err != nil {
- t.Error(err)
- return
- }
- if loaded.Credits != 110 {
- t.Errorf("after recovery: credits=%d, want 110 (win durable once; no stake refund on top)", loaded.Credits)
- }
- if open := h.g.Economy.mustLoadOpen(t); len(open) != 0 {
- t.Errorf("open records: %+v", open)
- }
- })
-}