Payouts were computed at completion from LIVE config, so an admin changing
multipliers mid-game would change the payout of an already-in-progress game.
- Migration 016 adds payout_escape_amount/payout_treasure_amount (BIGINT) and
the corresponding multipliers to the games table.
- Both game-start paths stamp the resolved payout amounts (computed from the
config in effect at entry) onto the game row.
- completeGame pays from the snapshot, falling back to live calculation only
for legacy games that predate the snapshot columns.
Adds payoutSnapshot.test.js proving a mid-game config change cannot alter an
in-flight payout. Full suite: 15 suites / 131 tests.
Money is integer atomic units, but payout and sum math ran on float64
(`Math.round(base * multiplier)`, `sum + Number(x)`), which silently loses
precision above 2^53 — a real risk for large/credit/XMR amounts.
- Add src/money/atomic.js: the single source of truth for atomic-unit math
(toBig, toSafe, sum, add, mulByDecimal, format). mulByDecimal multiplies an
atomic amount by a decimal multiplier using exact integer arithmetic with
half-up rounding; toSafe narrows to a number only when exactly representable
and otherwise returns a decimal string.
- calculatePayout now uses money.mulByDecimal/toSafe instead of float math.
- walletRPCService batch total + fee sum use money.sum instead of float reduce.
Adds moneyAtomic.test.js with property tests on amounts above 2^53 proving
exactness where the old float path drifted. Full suite: 14 suites / 129 tests.
transfer_split sends one on-chain tx to many destinations, so payout rows
in a batch share a tx_hash. The old UNIQUE index on payouts.tx_hash made
the 2nd+ row fail to record, leaving funds sent but rows stranded in
'processing' and re-sendable by the retry service.
- Migration 015 drops the payouts tx_hash unique index (idempotency is
enforced by idx_payouts_one_per_game) and extends that guard to cover
'processing' so no second payable row can be inserted mid-flight.
- Batch completion now updates the whole batch in a single transaction via
id = ANY(...), so rows can't be left half-completed. Per-user stats use
BIGINT arithmetic in SQL (no JS float).
- Ambiguous batch failures are marked 'needs_review' (which the retry
service does NOT pick up) instead of 'failed', preventing a double-send
when transfer_split may have already broadcast. Operator is alerted.
Adds batchPayout.test.js covering the shared-tx_hash completion and the
needs_review failure path. Full suite: 12 suites / 115 tests.
Phase 0.1 — Underpayment confirmation bug (CRITICAL):
Payment confirmation gated on `status.confirmed`, which checkPaymentStatus
sets for ANY incoming tx regardless of amount, so sending a single atomic
unit granted a full game/credits. Now gate on `status.complete` (received
>= expected). Underpayers are warned once and the payment stays pending so
a top-up can complete it. Persist `received_amount` for single-game and
credits confirmations for reconciliation. Adds an underpayment regression
test.
Phase 0.2 — Provably-fair was decorative (CRITICAL):
The committed seed had zero effect on the dungeon (created seededRNG was
unused; generation used Math.random + a globally-seeded ROT.RNG). Thread a
per-game seeded RNG derived from the committed seed through dungeon
generation (reseed the Digger via seedToInt + replace all Math.random) and
monster movement (seededShuffle). Add DungeonGenerator.regenerateFromSeed
and layoutFingerprint; /api/verify now regenerates the dungeon from the
seed and returns a fingerprint a player can independently reproduce. Adds a
determinism test suite.
Full suite: 11 suites / 113 tests passing.
Plan and progress tracked in docs/REMEDIATION_PLAN.md.
Payment Fixes:
- Prevent credits going negative with WHERE credits >= $1 check
- completeGame now uses recorded payment_mode from game record instead of global gameMode
- Add duplicate payout prevention by checking for existing payouts before processing
Dead Code Cleanup:
- Remove unused MoneroPayService (WalletRPCService is the actual service)
- Remove legacy MoneroPay references from comments
Tests:
- Add 50 comprehensive payment/payout tests (payment_payout_comprehensive.test.js)
- Expand WalletRPCService tests (4 -> 31 tests)
- Total: 106 tests passing across 10 test suites
Documentation:
- Update TODO.md with recent fixes and correct test counts
- Update migration comments to reflect actual wallet service used
- Add payout database records: processPayout now inserts into payouts
table with tx_hash, fee, status, and updates user statistics
- Fix credits deduction: QueueManager game start methods now properly
call processGameStart() to deduct credits (was being skipped)
- Fix credits_update emission: moved from GameModeManager (no io access)
to socketHandlers/queueManager where io is available
- Add payment recovery: users who disconnect during payment confirmation
now get credits recovered automatically on reconnect
- Remove dead handlePaymentDetected method that referenced undefined this.db