jwinterm 2ff8d0be67 feat: multi-node daemon failover and graceful degradation when nothing answers
Closes the two findings left open from the pre-launch review.

Node redundancy (it was node reachability, not chain health)
  RPC_ENDPOINTS accepts any number of daemons in preference order; PRIMARY /
  FALLBACK still work and are appended. The list is deduplicated, which exposes
  that the stock config pointed both at the SAME host — what looked like
  redundancy was one node.

  One logical call now walks every endpoint before failing, instead of needing
  three consecutive failures to try exactly one alternate. Each node still has
  to pass the chain-identity check before it may answer, so failover cannot
  silently serve another chain's data. `failoverActive` is no longer a one-way
  latch: the preferred node is re-tested every RPC_PREFERRED_RETRY_MS, so
  recovery no longer requires a restart. healthCheck probes every node.

Graceful failure
  Entries are refused while no node is reachable. A run is measured in blocks
  and its entry block comes from the last successful poll, so a game admitted
  during an outage was anchored to an already-past block and killed as a timeout
  on the first recovered poll — the player lost a run, and in paid mode an
  entry, to our infrastructure. The gate sits in _runAdmission, ahead of any
  credit spend, and says so to the player.

Wallet: refused, not failed over
  Two wallet-rpc processes over one wallet file can build transactions from the
  same outputs and corrupt the cache. A standby wallet is a custody decision for
  the operator, not something this code may take on its own — daemons are
  read-only and interchangeable, wallets are neither. So a wallet outage
  declines paid intake cleanly and leaves credits and free play working.

Auth limiter evicts instead of refusing
  The bounded key map 429'd any NEW ip once full, turning a memory cap into a
  DoS primitive: a distributed source could hold it at capacity and lock every
  first-time caller out of Smirk login. It now reclaims expired entries and, if
  still full, evicts the oldest live windows. Same memory ceiling, still a
  working limiter.

129 suites / 1026 tests pass.
2026-07-26 07:49:18 -04:00

Wownerogue

A browser-based roguelike synchronized with Monero (XMR) and Wownero (WOW) block timing. The same codebase runs the original solo dungeon, the social Tavern, and operator-selected PvP/race rulesets, with separate free and paid leaderboards.

Such Software-operated products

Such Software (apps@such.software) operates only these two public products:

Service Operated scope
play.wowne.ro Wownero mainnet free play and pay-for-credits leaderboard/prestige play. Credits are non-redeemable; there are no prizes, cash-out, or payouts, and the service is not offered or marketed as gambling. Legal classification depends on applicable law.
monerogue.app Monero stagenet only, with direct solo entry only. Single-player 2×/3× outcomes are test gambling mechanics using test coins with NO REAL VALUE. Purchased-credit entry and crypto-match payouts are off. Never send mainnet XMR.

The repository remains generally self-hostable under the MIT License. A third-party operator is solely responsible for its deployment, product, legal compliance, funds, players, claims, and support. Such Software does not operate, supervise, endorse, or accept responsibility for a third-party deployment.

Features

  • Provably fair solo runs with two-party, pre-entry commitments and per-depth fingerprints
  • Live spectator mode - watch active games in real-time
  • Persistent chat with 30-day history
  • Transaction history - view payment and payout records
  • Multiple game modes: Free play, per-game payments, or credit bundles
  • Anonymous social Tavern (no account required) with chat, live solo spectating, and in-place race queues
  • Shared render kit with Tiled, ASCII, Iso, and locally served 3D techniques across the dungeon and Tavern
  • Real-time multiplayer races: free, prestige-credit, or crypto winner-take-pot races every block
  • Configurable difficulty with house edge tuning
  • Automatic wallet RPC integration for payments and payouts

Quick Start

git clone <repository-url>
cd wownerogue/src
npm install
cp .env.example .env  # Edit with your database/wallet settings
npm run db:create     # Initialize PostgreSQL
npm run dev           # Start development server

Open http://localhost:3000 to play.


Game Modes

Mode Cost Payout Leaderboard
FREE None None Pleb
PAID_SINGLE Per-run chain payment Profile-controlled Paid prestige
PAID_CREDITS Purchased credit Profile-controlled Paid prestige

The exact Such Software production profiles are src/.env.mainnet.example (credits only, every payout path off) and src/.env.stagenet.example (direct-entry-only stagenet 2×/3× solo test payouts, purchased credits and crypto-match payouts off). Each opts into a startup contract with OPERATED_PRODUCT_PROFILE; preflight refuses network, operator-identity, or economic-scope drift. Generic self-hosts leave that variable unset.

Match Mode (Multiplayer Races)

Optional real-time multiplayer races are available when MATCH_ENABLED=true. A new race starts every crypto block as long as at least two players are queued. Supported economies:

Economy Entry Payout Leaderboard
Free None None Pleb
Prestige Credits Credits None Prestige
Crypto Race Race-entry ticket Winner-take-pot minus house fee Hall of Champions

See docs/MATCH_MODE.md for full architecture, configuration, and API details.

For deterministic multi-bot playtesting and 16:9/9:16 ad footage, use the loopback-only, free-only capture harness documented in docs/PVP_CAPTURE.md. It drives the real match engine over Socket.IO but cannot connect to a deployment, database, wallet, or payout path.

Difficulty & Pacing

Difficulty has two levers, both keyed to the crypto network so cryptoType actually shapes the game (it used to be a dead parameter).

Presets (DIFFICULTY_PRESET) — the per-level shape:

Preset Dungeon Size Monster Target House Win
easy 30×15 low ~30%
normal 45×22 medium ~55%
hard 55×28 high ~65%
casino 70×35 very high ~70%

Defaults to normal for free play, casino for paid. Every knob is env-overridable (DUNGEON_WIDTH, MONSTER_SPEED, …).

Multi-level depth (levels) — the pacing lever, ∝ block time. A run descends N levels (each a preset-sized dungeon with a fair monster). Reaching a non-final exit takes the stairs down; only the final exit escapes; the treasure sits in the vault (final level). Because total run length scales with the chain's block time, the "race the block" timer provides the house edge — no giant map, no cheating-fast monster.

Chain Block time Levels
GRIN ~1 min 1
XMR ~2 min 2
LTC ~2.5 min 2
WOW ~5 min (measured) 4
BTC ~10 min 8

Levels live in NETWORK_TUNING (src/game/difficultyConfig.js); override with DUNGEON_LEVELS, disable with NETWORK_TUNING_DISABLED=true. The counts are sim-derived starting points — see docs/BALANCE_SIM.md for how the balance is measured and calibrated.

Note: Wownero's block time is ~5 min (measured on the live daemon), not 2 min as older docs/comments claimed.


Key Features

Provably Fair Gaming

  1. The server publishes a one-use, socket-bound SHA-256 commitment before paid entry.
  2. The browser adds an independent WebCrypto client seed; neither side alone selects the final seed.
  3. The effective seed deterministically generates each dungeon depth and its audit fingerprint.
  4. After completion, the server seed and depth manifest are revealed at /verify/:gameId and /api/verify/:gameId; active-game server seeds remain private.

Spectator Mode

Click "👁️ Watch Games" to see active games. Spectators receive real-time updates via Socket.IO rooms. Press ESC to exit.

Transaction History

Click "📜 History" to view your payment and payout records. Shows:

  • Total received from payouts
  • Payment history with status and credits received
  • Payout history with multipliers and transaction hashes

Session Persistence

Sessions, including anonymous Tavern entry, use an anonymous token stored in localStorage:

  • ✅ Persists across refreshes, browser closes, server restarts
  • ⚠️ Lost when clearing cookies/localStorage, using incognito mode, or switching browsers/devices

A warning appears if localStorage is unavailable.


API Reference

Public Endpoints

Endpoint Description
GET /health Redacted dependency/readiness summary
GET /health/live Process liveness probe
GET /health/ready Dependency readiness probe (503 while degraded)
GET /api/game-modes Current modes and pricing
GET /api/leaderboard?board=pleb|champions|prestige&period=all|week|month One explicitly separated score board; an omitted board means pleb, while legacy all, blank, and unknown board values return 400
GET /api/user/:socketId/credits User credit balance
GET /api/user/:socketId/payments Payment history (paginated)
GET /api/user/:socketId/payouts Payout history (paginated)
POST /api/user/:socketId/address Set payout address
GET /verify/:gameId Human-readable fairness verification page
GET /api/verify/:gameId Machine-readable fairness proof

Admin Endpoints

Require X-Admin-Key header matching ADMIN_API_KEY env variable.

Endpoint Description
POST /api/admin/refund/payment Request an idempotent refund; consumed grants require review
POST /api/admin/credits/adjust Add or remove credits from a user
GET /api/admin/users/search Search users by socket ID or address

Example: Refund a payment

curl -X POST http://localhost:3000/api/admin/refund/payment \
  -H "Content-Type: application/json" \
  -H "X-Admin-Key: your-admin-key" \
  -d '{"paymentId": 123, "reason": "Customer request", "sendFunds": true}'

Example: Adjust credits

curl -X POST http://localhost:3000/api/admin/credits/adjust \
  -H "Content-Type: application/json" \
  -H "X-Admin-Key: your-admin-key" \
  -d '{"socketId": "abc123", "amount": 5, "reason": "Compensation"}'

Socket.IO Events

Client → Server

Event Description
auto_start Start game (uses credits if available)
player_move Move player (direction: 0-3)
request_payment Request payment address
get_active_games List games for spectating
spectate_game Start spectating a game

Server → Client

Event Description
game_start Game initialized with dungeon state
game_update State after movement (incl. depth/maxDepth for multi-level descent)
game_event Discrete event: treasure_found, descend (took the stairs down), escaped
game_over Game ended
payment_created Payment address ready
credits_update Credit balance changed
spectator_update Spectated game state

Configuration

Key environment variables for a generic self-host (not an operated production profile; see .env.example for the full list):

# Core
NODE_ENV=production
PORT=3000
DIFFICULTY_PRESET=casino

# Payments
PAYMENTS_ENABLED=true
PAYMENT_MODES=direct,credits
DIRECT_GAME_PRICE=100000000000    # 1 WOW in atomic units
CREDITS_PACKAGES='[{"id":"small","credits":10,"price":"500000000000","bonus":0}]'

# Payouts
DIRECT_PAYOUT_ESCAPE=2.0
DIRECT_PAYOUT_TREASURE=3.0

# Wallet RPC (required for paid modes)
PRIMARY_WALLET_ENDPOINT=http://127.0.0.1:34570
WALLET_RPC_USER=user
WALLET_RPC_PASSWORD=use-a-distinct-strong-rpc-secret

# Database
DB_HOST=localhost
DB_NAME=wownerogue
DB_USER=your_user
DB_PASSWORD=your_password

# Admin API (generate with: openssl rand -hex 32)
ADMIN_API_KEY=your-secure-key

# Network (Monero only - Wownero only has mainnet)
MONERO_NETWORK=mainnet

Amounts use atomic units: 1 WOW = 10^11 atomic units.


Production Deployment

See docs/DEPLOY.md for detailed production deployment instructions including:

  • systemd service configuration
  • Nginx/reverse proxy setup with WebSocket support
  • Database permissions and security hardening
  • Multi-instance deployment (running Wownero and Monero on the same server)

The dated play.wowne.ro production record distinguishes the live schema-neutral renderer/Tavern overlay from the still-gated full mainline migration cutover.


Operations

See docs/LOGS_AND_BACKUP.md for:

  • Log management with systemd/journald
  • PostgreSQL automated backups
  • Backup verification procedures

Testing

cd src
npm test

Tests cover payment handlers, wallet RPC, security rules, movement logic, and integration flows.


Project Structure

src/
├── index.js                 # Entry point
├── config/                  # Payment config, validation
├── db/                      # PostgreSQL layer
├── game/                    # Dungeon, player, monster logic
├── network/                 # Socket handlers, chat, queue, spectator
├── payments/                # Wallet RPC, QR codes
└── utils/                   # Errors, memory management
html/
├── index.html               # Game client
├── admin.html               # Admin dashboard
└── js/                      # Frontend modules
test/
└── *.test.js                # Jest tests

License

The source is available under the MIT License. MIT permission to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies is subject to including the copyright and permission notice in all copies or substantial portions. The software is provided “AS IS”, without warranty of any kind as stated in the License.

The MIT License governs rights in the source. Documentation, examples, and product disclosures are informational only; they are not legal advice or a determination that a deployment complies with applicable law. Such Software operates only the two products and scopes listed above. The MIT License does not make an independent deployment a Such Software service or authorize it to claim Such Software sponsorship or endorsement; its operator remains solely responsible for the service.

S
Description
Mirror of github.com/Such-Software/wownerogue
Readme MIT
8.4 MiB
Languages
JavaScript 92.2%
HTML 4.2%
PLpgSQL 2.3%
CSS 0.8%
Shell 0.4%
Other 0.1%