SPB Git forge

spb/spinza

Public
8commits 1branches 0releases
1.6 MBsize
maindefault branch
16 days agolast push
TypeScript 97.6% SQL 1.4% JavaScript 0.5%
6.0 KB · 61 lines markdown
Rendered Raw Blame History
1# Spinza — repository guide23Spinza (www.spinza.dev) is a premium **fictional** social casino. Virtual credits only: no deposits, no withdrawals, no purchases, no crypto, no cash value, no prizes. The full product brief is in `docs/SPEC-original.md`; this file is the condensed working guide.45## Layout (pnpm workspaces + Turborepo)67| Path | Package | Role |8|---|---|---|9| `packages/shared` | `@spinza/shared` | Economy constants (10,000 SC start, bet levels, daily rewards, XP curve), zod schemas, formatting (`formatSC` — never a currency sign), API DTO types. |10| `packages/game-core` | `@spinza/game-core` | **Also**: `src/crash/` (crash engine: `P(crash ≥ x) = rtp/x`, SHA-256 commit/reveal, curves exp/power/steps, pacing events, `multiplierAt` shared with the browser via `crash/curve.ts`) and `src/arcade/` (instant resolvers + ladder engine `m_k = rtp·Π(1/p_i)` — strategy-independent RTP). |11| | | **Server-only game engine**: `CryptoRng` (crypto.randomBytes, rejection sampling — `Math.random` is banned for outcomes), reels, ways/lines evaluation, wild/scatter/cascade/hold-respin/pick-bonus/meter/heat/mystery/quantum/dynamic-grid features, `runSpin()`, `simulate()`, `certify()`, `validateDefinition()`, `defineGame()`. `./client` export = types only (safe for the browser). |12| `games/` | `@spinza/games` | 20 slot definitions (`src/<slug>/index.ts`, `registry.ts`), 10 **Risk Games** (`src/crash/index.ts`, cash-out/crash engine), 5 **Spinza Originals — Beyond Slots** (`src/arcade/index.ts`: Dropzone, Grid//Break, Orbit = instant; The Vault, Escape 99 = ladder), `calibration.json` (payScale per slug+version), `certifications/<slug>.json` (PASS/FAIL reports for all 35 games). |13| `packages/database` | `@spinza/database` | Drizzle schema (25 tables, snake_case casing), migrations in `drizzle/`, `seed.ts` (games sync, achievements, missions, levels, flags, settings), `sync-games.ts`. |14| `apps/api` | `@spinza/api` | Fastify on 127.0.0.1:8231. Auth (argon2id, opaque 256-bit sessions hashed, recovery codes), atomic spin (`services/spin.ts`), wallet ledger (`services/wallet.ts`), progression, rewards, leaderboards, admin (TOTP), health. |15| `apps/simulator` | `@spinza/simulator` | CLI `pnpm sim <validate|run|calibrate|certify>` with worker threads; also used by the admin simulator. |16| `apps/web` | `@spinza/web` | Next 16 (port 8230). `/api/*` is rewritten to the Fastify service. Game screen = `src/components/game/*` (PixiJS renderer, procedural symbols, WebAudio sound). Admin console under `/admin`. |1718## Game families & API1920| Family | Kind | Play API | Client |21|---|---|---|---|22| Slots (20) | `slot` | `POST /api/games/:slug/spin` | `components/game/*` (PixiJS renderer) |23| Risk Games (10) | `crash` | `POST /api/crash/:slug/start` → poll `GET /api/crash/:slug/rounds/:id` → `POST /api/crash/:slug/cashout` (auto cash-out settles server-side; `crash_rounds` table; lazy settlement) | `components/crash/*` (2D canvas scenes) |24| Beyond Slots instant (3) | `arcade` | `POST /api/arcade/:slug/play {bet, clientRoundId, input}` | `components/arcade/{dropzone,gridbreak,orbit}.tsx` |25| Beyond Slots ladder (2) | `arcade` | `POST /api/arcade/:slug/start` → `POST /api/arcade/:slug/act {roundId, action}` (`arcade_sessions`) | `components/arcade/{vault,escape}.tsx` |2627Every settled round of any family goes through `apps/api/src/services/settle.ts` (`settleRound`: game_rounds row, stats, XP, achievements, missions, leaderboards). Lobby categories come from `summary.kind`/`category` written by `sync-games.ts`.2829## Non-negotiable rules3031- Outcomes are decided **only** in `apps/api` via `runSpin`. The browser animates `result.steps`; it never computes wins.32- Every balance change goes through `lockWallet` → `applyCredit` (ledger row) → `saveWallet` inside one Postgres transaction. `sum(credit_transactions.amount) == wallets.balance` is an invariant checked in admin.33- Spin idempotency: unique `(user_id, client_round_id)`; duplicates (even concurrent) return the stored round with `replayed: true`.34- A game is `published` only if `games/certifications/<slug>.json` is `PASS` for its exact `version`. Changing pays/weights ⇒ bump `version`, re-run `pnpm sim calibrate <slug>` then `pnpm sim certify <slug>`.35- Never log passwords, hashes, recovery codes, session tokens (pino redact list in `apps/api/src/app.ts`).36- No payments, no `$`/`€` anywhere in UI copy. Credits are formatted `10,000 SC`.3738## Commands3940```bash41pnpm install42createdb spinza && pnpm db:migrate && pnpm db:seed          # local Postgres 17 + Redis required43pnpm dev:api   # 8231     pnpm dev:web   # 823044pnpm sim validate | pnpm sim run <slug> --spins 1000000 | pnpm sim calibrate all|arcade --spins 10000000 --threads 26 | pnpm sim certify all|crash|arcade --spins 1000000045pnpm --filter @spinza/game-core test    # engine unit tests46pnpm --filter @spinza/api test          # integration tests (needs local DB/Redis)47pnpm admin:create <username> [password] # prints TOTP secret once48```4950## Deployment (MacLustr)5152`mld deploy spinza` from the laptop (`~/Desktop/cluster-skill/mld`). Manifest with secrets: `M1M32:~/dispatch/apps/spinza.json` (copy: `deploy/spinza.mld.json`, gitignored). Node M3U96a, PM2 `spinza-api` (8231 loopback) + `spinza-web` (8230) + `spinza-ngrok` (`www.spinza.dev`). Postgres db `spinza` + Redis local to the node. Hooks: install → migrate + seed → `next build`. Backups: `deploy/backup.sh` (daily launchd on the node).5354## Gotchas5556- drizzle-orm ≥0.44 wraps pg errors: use `pgCode(e)` (`apps/api/src/lib/pg.ts`), not `e.code`.57- Way/line pays are in bet units (`betDivisor`: 25 for 5-reel ways, 50 for 6+ reels, `lines.length` for line games); scatter/bonus/coin values are × total bet. `payScale` from `calibration.json` scales all non-jackpot wins.58- Certification tolerance is statistical: `max(0.4%, 3σ/√n)` capped at 1.5% — high-volatility games need ≥10M spins.59- Turbopack: import workspace packages without `.js` extensions; browser code imports `@spinza/game-core/client` only.60- PixiJS renderer must be created client-side only (`GameClient` is `"use client"`; `Application.init` is async).61