Commit Graph
49 Commits
Author SHA1 Message Date
jwinterm 63e3006aca launch features: win feed, public stats, share-a-win, live CTA
Four pre-launch additions to make the game feel alive and shareable:

- Live win feed: server broadcasts 'win_feed' on every escape; client shows a
  transient toast ('X escaped with the bag') so the room feels active.
- Public stats: GET /api/stats (10s-cached) returns games + escapes in the last
  24h and total paid out; a status-area strip polls it for social proof.
- Share-a-win: game-over screen gets 'Share to X' + 'Copy link' buttons pointing
  at the /verify page, which now renders OpenGraph/Twitter-card meta (brand OG
  image per instance) so shared links unfurl with a preview.
- Land-on-action: a dismissible 'N games live now — watch' CTA appears on connect
  when games are in progress, opening the existing spectator panel. Non-intrusive:
  hidden while playing/spectating and never hijacks the canvas.
2026-06-01 09:17:48 -04:00
jwinterm db5ed2e8aa payouts: defer when Monero outputs are locked instead of failing
Spent outputs (including change) lock for ~10 blocks on Monero. When a
payout batch has no unlocked balance to cover it, attempting transfer_split
fails noisily and, for batches, gets parked in needs_review for an operator.

Add a pre-flight unlocked-balance check that reverts the batch to 'pending'
(re-checked every new-block tick and batch interval) so it sends as soon as
outputs unlock — no manual intervention. Also classify insufficient-funds
RPC errors as pre-broadcast/safe-to-retry: single -> pending (not failed),
batch -> pending without an operator alert (not needs_review), since no tx
went on-chain and there's no double-pay risk.
2026-06-01 08:17:05 -04:00
jwinterm e6936f47d9 feat: network-aware currency label (sXMR), game name, and Smirk gating
- Currency display is now network-aware: XMR on a test network shows sXMR
  (stagenet) / tXMR (testnet) so it's clear the coins aren't mainnet; mainnet
  XMR shows XMR, Wownero stays WOW. A fork running mainnet automatically shows
  XMR. cryptoType (used for decimals/QR asset) is unchanged.
- Product name (Monerogue/Wownerogue) is derived from the currency and no
  longer hardcoded: the chat "Connected to <name> server" message, payment
  descriptions, and the verify page title all follow it.
- Smirk is forced off on any test network (stagenet/testnet) regardless of
  SMIRK_ENABLED, since Smirk only works on mainnet — so forks don't have to
  remember the env flag.

New shared helpers (gameNameFor, currencyLabelFor, isTestNetworkFor); exposed
via game_mode_info (currencyLabel, gameName). Full suite: 22 suites / 159 tests.
2026-05-31 15:57:59 -04:00
jwinterm d8d10728ba fix: gate direct (entry-fee) payouts behind DIRECT_PAYOUTS_ENABLED
completeGame only gated PAID_CREDITS payouts (on creditsPayoutEnabled); a
PAID_SINGLE win paid out unconditionally, so DIRECT_PAYOUTS_ENABLED=false did
NOT actually stop direct payouts — a no-payout instance (mainnet legitimacy)
would still send crypto on an entry-fee win. Caught during deploy verification.

- Add this.directPayoutEnabled (from DIRECT_PAYOUTS_ENABLED env / direct rule
  enabled in the config snapshot), mirroring creditsPayoutEnabled.
- completeGame now gates the PAID_SINGLE payout on directPayoutEnabled.
- The derived directPayoutsEnabled flags (getGameModeInfo / effective mode)
  and /api/game-modes PAID_SINGLE.payoutMultiplier now respect it (show 0 when
  off) instead of inferring from multipliers > 0.

Adds a regression test: a PAID_SINGLE win with directPayoutEnabled=false
queues no payout. Full suite: 22 suites / 159 tests.
2026-05-31 15:25:46 -04:00
jwinterm 988617289b feat: free-play + dual-leaderboard frontend
- Leaderboard panel: two boards via tabs — 🏅 Hall of Champions (paid) and
  🪙 Pleb (free) — default Champions; passes board= to /api/leaderboard and
  retitles per board.
- PaymentUI: the Free button now shows whenever free play is offered (instance
  free-only OR free-as-a-choice via freePlayEnabled), labelled "Play Free / no
  payout / Pleb leaderboard" on paid instances, and emits the play_free event
  (records game_mode=FREE). Replaces an emit of the dead 'enter_game' event.
- Server game_mode_info payload now includes freePlayEnabled so the client can
  show the choice.

Full suite: 22 suites / 158 tests.
2026-05-31 15:08:54 -04:00
jwinterm 0a1766ee2a feat: free-play choice + per-game dual leaderboard (backend)
Free-to-play alongside paid, with scores split per game between a Pleb board
(free) and the Hall of Champions (paid). Keys on the existing per-game
game_mode, so no new game columns.

- Leaderboard API: GET /api/leaderboard?board=pleb|champions|all. champions =
  game_mode IN (PAID_SINGLE,PAID_CREDITS), pleb = FREE. board is whitelisted.
  Migration 019 adds a supporting partial index.
- FREE_PLAY_ENABLED config: offer free play as an explicit choice even when the
  instance also sells credits/entry (always on when payments are off).
- play_free socket event -> handleAutoStart(socket, {free:true}); skips all
  payment/credits/payout-address gating and records the game as game_mode=FREE
  via processGameStart(..., {forceFree:true}) -> _processGameStartFree (no
  payment, no payout, counted).
- getPaymentOptionsForUser exposes a play_free option; /api/game-modes exposes
  freePlayEnabled.

Adds freePlay.test.js. Full backend suite: 22 suites / 158 tests.
2026-05-31 15:01:01 -04:00
jwinterm 7d3456f107 fix: dungeon reachability guard + per-substep monster collision
3.2 Reachability: a dungeon whose exit is unreachable from the entrance is
paid-but-unwinnable. generate() now BFS-checks entrance->exit and regenerates
if unreachable. Retries are deterministic (per-attempt ROT seed = seedInt +
attempt; the seeded rng stream keeps advancing), so verify-regeneration
replays the same attempts and reproduces the exact dungeon — the provably-fair
determinism from Phase 0.2 is preserved. The single-pass body is extracted to
_generateOnce and isReachable does the flood-fill.

3.3 Monster collision: with movesPerPlayerMove > 1 (the casino preset's 1.5x
speed) the monster takes multiple steps per turn, but collision was only
checked after the loop — so it could step onto the player's tile and off again
between checks, phasing through and missing the catch (player-favorable, erodes
the house edge). Collision is now checked after each sub-step.

Adds reachability tests (provablyFair.test.js) and monsterCollision.test.js.
Random block timing remains the untouched core mechanic. Full suite: 20 suites
/ 153 tests.
2026-05-31 14:08:45 -04:00
jwinterm 41676fa072 refactor: unify identity resolution on stable user_id (anon_token)
There were two identity systems: SessionManager keyed on the stable, UNIQUE
anon_token, and getOrCreateUser keyed on the mutable, non-unique socket_id.
The latter could create a duplicate "orphan" user row (or resolve the wrong
row) when the socket_id lookup raced the session's socket_id update —
desyncing credits and payouts between the two rows for one human.

- getOrCreateUser now resolves through SessionManager's stable identity first
  (anon_token -> users.id), re-selecting a fresh row by stable id, and only
  falls back to the legacy socket_id lookup when no session exists. The
  last-resort create now warns, since it indicates a missing session.
- gameModeManager.sessionManager is wired in SocketHandlers so the
  session-first path is active in production.
- anon_token is already UNIQUE (migration 003), so the stable-identity
  constraint already exists; no new migration needed.

Re-keying the in-memory activeGames map by user_id is deferred (invasive;
already mitigated by stamping dbUserId on the game in the disconnect fix).

Adds identityResolution.test.js proving no duplicate row is created when a
session exists. Full suite: 17 suites / 136 tests.
2026-05-31 10:15:08 -04:00
jwinterm f9c6ce59d2 fix: CHECK constraints on money-table statuses; cover processing in payout guard
A typo in a status string (e.g. 'complete' vs 'completed') silently produced
a money row no index/predicate matched, orphaning funds.

- Migration 017 adds idempotent NOT VALID CHECK constraints on payments.status,
  payments.payment_type and payouts.status. NOT VALID enforces all new writes
  without failing on any pre-existing rows; conname guards make it re-runnable.
- The in-code existing-payout guard in completeGame now also covers the
  'processing' status (matching the unique index from migration 015), so a
  duplicate win can't create a second payable row while a payout is mid-flight.

Full suite: 15 suites / 131 tests.
2026-05-31 09:57:56 -04:00
jwinterm 473a4091d4 fix: snapshot payout terms at game start (what-you-saw-is-what-you-get)
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.
2026-05-31 09:55:45 -04:00
jwinterm 24aed355c6 refactor: exact BigInt money math via src/money/atomic.js
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.
2026-05-31 09:45:58 -04:00
jwinterm d07967c3d1 security: fix batch-payout tx_hash collision and double-pay risk
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.
2026-05-31 09:26:14 -04:00
jwinterm 3e6e3b858b security: fix underpayment exploit and make provably-fair real
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.
2026-05-31 09:14:21 -04:00
jwinterm a61851b0f7 add smirk payments and leaderboard and diff tuning 2026-02-20 13:54:36 -05:00
jwinterm 27660d6948 Fix rewards, make tx hash clickable, add win payout notification, and add entry choice modal 2026-02-20 12:34:21 -05:00
jwinterm da6763886a fix credits display p3 and update .env.example 2026-02-20 11:11:51 -05:00
jwinterm 5776c58357 fix credits package bug and improve stale payment logic 2026-02-19 07:05:15 -05:00
jwinterm bc55f0355e fix edge cases and tests 2026-02-19 06:15:45 -05:00
jwinterm ec3caabf1c feat: initial batch payout logic and output splitting script added 2026-02-18 07:30:46 -05:00
jwinterm bf5938c366 fix game entry and payout bugs 2026-02-18 06:35:32 -05:00
jwinterm 320d1a3b1f fix admin panel and credits package 2026-02-17 20:02:22 -05:00
jwinterm 6cf3961da0 feat: add smirk toggle and more admin capabilities 2026-02-02 08:42:39 -05:00
jwinterm 9a53df4a35 Fix critical double pay bugs 2026-02-01 16:38:51 -05:00
jwinterm 2286d8deff Feat: add smirk login
Fix: add security hardening and transaction database commits
2026-01-28 17:21:53 -05:00
jwinterm 38a7e58e96 fix: payment flow 2026-01-13 18:49:59 -05:00
jwinterm ce58a33347 Fix payment bugs, add comprehensive tests, remove dead code
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
2026-01-13 16:45:11 -05:00
jwinterm 00f8057cb5 Fix credits mode payment processing and payout tracking
- 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
2026-01-02 14:19:11 -05:00
jwinterm aec1c71a8c add early entry and help screens for new users 2025-12-26 10:21:25 -05:00
jwinterm f1c86a084b Fix payout calculation - update defaults and add debug logging 2025-12-24 21:00:22 -05:00
jwinterm b062a1a5fa debug payouts 2025-12-24 20:54:07 -05:00
jwinterm 2c781ec043 fix payout calcs 2025-12-24 20:03:26 -05:00
jwinterm e218fbf67b fix payment UI 2025-12-24 17:24:04 -05:00
jwinterm 3589406bac fix payment addy bug 2025-12-24 16:45:35 -05:00
jwinterm 7f91d83308 fix monster bug and let spectators join pending games 2025-12-22 11:25:22 -05:00
jwinterm 24f46fa35e fix chat and payments and add spectator mode 2025-12-22 10:36:46 -05:00
jwinterm fbf5bd5b17 add stagenet warning and increase diff of game and also add modes 2025-12-21 15:10:40 -05:00
jwinterm aa419689c8 fix multimode payments and get credits working again 2025-12-21 14:08:20 -05:00
jwinterm fa61d60bc0 Limit payment requests 2025-09-30 07:03:41 -04:00
jwinterm 4e7a773496 add address modal and try to fix payout 2025-09-29 17:40:59 -04:00
jwinterm 95711b9d26 add error handling and more tests 2025-09-29 11:20:11 -04:00
jwinterm c7cbdff021 UnifyPayments (megachange) 2025-09-29 08:49:27 -04:00
jwinterm 9a4911f410 fix audio and add new audio and fix qr and db index 2025-09-28 10:43:48 -04:00
jwinterm f8241dd309 Continue working on payment logic 2025-09-27 11:21:11 -04:00
jwinterm e3915dcde2 Begin payment logic and tests 2025-09-27 08:45:22 -04:00
jwinterm 4ad063f735 Get back into it 2025-09-24 14:06:42 -04:00
jwinterm bd97d9d848 First (untested) payment and db setup 2025-06-01 14:17:29 -04:00
jwinterm f02e16272e refactor frontend and fix debug mode 2025-05-29 13:40:44 -04:00
jwinterm de3eed792b waiting anim working 2025-05-28 20:05:51 -04:00
jwinterm 50ccf28a04 refactor backend folder structure 2025-05-26 20:04:25 -04:00