# Rover Analytics — setup and feature notes

This is the expanded version of your existing website. Keep this file with the source ZIP.

## 1. What is included

| Area | Features |
| --- | --- |
| Live | CCU, active servers, playtime including active sessions, recent events, ingestion freshness |
| Retention | D1, D7, D30, cohort tables, observed active users and stickiness |
| Discovery | Automatic Roblox imports for qPTR, PTR, impressions, acquisition sources |
| Official reports | Roblox playtime, retention, revenue, paying users, FPS, memory and crash metrics |
| Spending | Rolling 72-hour unique/repeat spenders, repeat rate, previous-window comparison, products, spender tiers, purchase timing, repurchase intervals, purchase paths |
| Behaviour | Session lengths/gaps, early exits, zones visited, progression, shop activity, ordered funnels and journeys |
| Players | Pseudonymous profiles, history, segments, segment migration, playtime/spending leaderboards |
| Investigations | Player/session/server/event searches, chronological events, attribute comparisons, suspicious-activity flags |
| Economy | Currency sources/sinks and latest recorded wallet balances |
| Comparisons | Games, equal periods, cohort revenue, release before/after, 50/50 A/B experiments |
| Reports | Saved charts, CSV/PDF reports, data-quality checks, dashboard alerts |
| Accounts | Owner/Game Manager/Developer roles, individual passwords, assignments, blocking, removal and password resets |
| Security | Hashed passwords, hashed ingest keys, key regeneration, secure sessions, origin checks, rate limits, account/game audit logs and hourly key usage |
| Operations | Database migrations, Cloudflare deployment preparation, scheduled imports/cleanup, backup and restore scripts |

No live Roblox universe or private Roblox API key has been supplied during development. The software must be connected to your game before live numbers can appear. A local test is not proof of uninterrupted production operation.

## 2. Two separate deployments

The existing `chatgpt.site` address is an owner-only preview. Its outer ChatGPT access gate remains private. Creating developer accounts inside Rover does not bypass that gate.

For your team to use independent usernames/passwords, deploy the dashboard to your own Cloudflare account. Use the same source code; no redesign is required. The public collector is a second Worker with an authenticated ingest endpoint. Its dashboard administration endpoints require a separate private secret.

- Dashboard: accounts, permissions, game registry, browser interface.
- Collector: telemetry, detailed analytics, official Roblox connections, scheduled imports and alerts.
- Each Worker has its own D1 database. The same migrations apply to both.
- `COLLECTOR_URL` and `COLLECTOR_DASHBOARD_KEY` connect the dashboard to the collector.
- If no collector is configured on a self-hosted dashboard, direct `/api/ingest` and local analytics work, and the dashboard's scheduled handler can import reports. The two-Worker deployment below is recommended and is required when keeping the private Sites dashboard.

## 3. Deploy to your Cloudflare account

Install Node.js 22.13 or newer (Node 24 recommended) and pnpm. In the extracted source directory:

```powershell
pnpm install --frozen-lockfile
pnpm exec wrangler login
pnpm check
pnpm build
pnpm exec wrangler d1 create rover-dashboard
pnpm exec wrangler d1 create rover-telemetry
```

Record the two database IDs returned by Cloudflare.

Prepare the dashboard configuration:

```powershell
node scripts/prepare-cloudflare.mjs YOUR_DASHBOARD_DATABASE_ID rover-dashboard
pnpm exec wrangler d1 migrations apply rover-dashboard --remote --config work/cloudflare/wrangler.json
pnpm exec wrangler secret put BOOTSTRAP_TOKEN --config work/cloudflare/wrangler.json
pnpm exec wrangler secret put COLLECTOR_DASHBOARD_KEY --config work/cloudflare/wrangler.json
```

Generate random secrets with a password manager: at least 32 random characters. Keep BOOTSTRAP_TOKEN different from COLLECTOR_DASHBOARD_KEY. Do not put secrets in source files.

In `collector/wrangler.jsonc`, replace `REPLACE_WITH_YOUR_DATABASE_ID` with the telemetry database ID. Choose a unique Worker name if needed. Then:

```powershell
pnpm exec wrangler d1 migrations apply rover-telemetry --remote --config collector/wrangler.jsonc
pnpm exec wrangler secret put DASHBOARD_KEY --config collector/wrangler.jsonc
pnpm exec wrangler secret put CONNECTOR_SECRET --config collector/wrangler.jsonc
pnpm exec wrangler deploy --config collector/wrangler.jsonc
```

Use the SAME value for the collector's DASHBOARD_KEY and the dashboard's COLLECTOR_DASHBOARD_KEY. CONNECTOR_SECRET must be a separate random secret of at least 32 characters. It encrypts outbound Roblox keys. Losing or changing it requires reconnecting the Roblox accounts.

Set `COLLECTOR_URL` on the dashboard to the HTTPS `workers.dev` origin returned by the collector deployment (no `/ingest` suffix). You can use:

```powershell
pnpm exec wrangler secret put COLLECTOR_URL --config work/cloudflare/wrangler.json
pnpm exec wrangler deploy --config work/cloudflare/wrangler.json
```

Leave TRUST_SITES_AUTH and SITES_OWNER_EMAIL unset on your own Worker. These are only for the existing private Sites installation. Never enable trust in OpenAI forwarding headers on a directly accessible self-hosted Worker.

Open the dashboard URL. Create the owner account with your BOOTSTRAP_TOKEN. This can happen only once. Remove BOOTSTRAP_TOKEN after successful setup. Passwords must have 16–128 characters. Keep the owner credentials in your password manager.

Add your own domain using Cloudflare's Worker domain settings if desired. Test the HTTPS URL; secure cookies are enabled for HTTPS. The two Workers and scheduled jobs run without your computer being on.

## 4. Add games and developers

1. Sign in as owner and open **Connections & data health**.
2. Enter the experience name and numeric universe ID from Creator Hub.
3. Save the displayed ingest key immediately. Rover stores only its SHA-256 hash.
4. Open **Developers**, create each developer's account and temporary password, and select their games.
5. Give credentials to each developer privately. They must change their temporary password on first login.
6. Blocking, removing, resetting a password, or logging out revokes relevant sessions. A session expires after 12 hours.

The owner and Game Managers see and manage all games, including game registration, ingest keys, official API connections and thresholds. Developers only see assigned games, including API requests, exports and lookups, and can create charts, experiment definitions and release notes for those games. Owners and Game Managers can manage normal developers. Only the owner can grant/remove Game Manager access or manage Game Manager accounts. The owner account is protected from all Game Manager administration. In Developers, select an account, choose Developer or Game Manager, then select Update role. Role changes revoke existing sessions for the target account. Additional owner creation is not supported.

To regenerate a key, use **Game settings & alerts**. The old key is revoked. Replace the corresponding Creator Hub secret immediately. The website never displays a previously saved plaintext key.

## 5. Install Roblox scripts

Download these files from the website or find them in `public/downloads/`:

- `Tracker.luau` — ModuleScript named **Tracker** in ServerScriptService.
- `Initialize.server.luau` — Script in the same folder.
- `GameHooks.luau` — examples for your existing server gameplay code; merely installing this example module does not wire it into your game.
- `Performance.client.luau` and `Performance.server.luau` — optional client FPS sampling. Follow their comments for placement; these are untrusted client observations, separate from official Roblox performance reports.

Edit the initialization script:

```lua
Endpoint = "https://YOUR-COLLECTOR.workers.dev/ingest",
SecretName = "ROVER_TELEMETRY_KEY",
Release = "your-current-release",
HeartbeatSeconds = 30,
BatchSeconds = 7,
Experiments = true,
```

In Creator Hub, add a secret named ROVER_TELEMETRY_KEY containing this game's ingest key and scope it to the collector hostname. Enable HTTP Requests. Publish the scripts in every relevant place in the universe. Never put keys in LocalScripts or ReplicatedStorage. Do not enter your Roblox account password into Rover.

The tracker keeps a random persistent pseudonym in your game's DataStore. Your raw Roblox UserId is not sent to Rover. If pseudonym storage fails, the player is skipped and tracking diagnostics increase; gameplay continues.

Joining, leaving and heartbeats automatically cover playtime, active players and observed retention. Heartbeats default to 30 seconds and batches to about 7 seconds, with dashboard refresh every 15 seconds. The intended playtime delay is approximately 30–60 seconds under normal conditions; this is a target, not a guarantee.

## 6. Add gameplay hooks

Call these methods from existing SERVER code after validating the action:

```lua
Tracker.Zone(player, "Lobby")
Tracker.Progress(player, "start", "Level1", 0)
Tracker.Progress(player, "complete", "Level1", elapsedSeconds)
Tracker.Shop(player, "open")
Tracker.Shop(player, "view", productId)
Tracker.Shop(player, "prompt", productId)
Tracker.Funnel(player, "tutorial", attemptId, stepNumber)
Tracker.Economy(player, "Coins", "source", 25, "quest")
Tracker.Wallet(player, "Coins", currentBalance)
Tracker.SecurityFlag(player, "impossible_speed", measuredSpeed, allowedSpeed)
```

Use a stable attempt ID across one funnel's steps and a new ID when the attempt restarts. Steps must be consecutive: 1, 2, 3, etc. Raw shop stage totals alone are not proof that the same users completed the stages in order.

For developer-product purchases, call `Tracker.Receipt(player, receiptInfo)` AFTER your existing idempotent fulfillment succeeds. Keep your existing ProcessReceipt or receipt-handler logic. Never grant twice, replace the purchase handler with analytics code, or delay granting a purchase because Rover is unavailable. PromptProductPurchaseFinished is not purchase verification.

Direct spender counts and Robux totals currently cover verified developer-product receipts delivered to this hook. Gamepass ownership is not equivalent to a new purchase, and list prices are not reliable paid amounts. Use the separately imported official revenue reports for other Roblox revenue categories. Rover does not invent per-player gamepass transactions from aggregate reports.

Examples are in GameHooks.luau. Client-facing shop interactions must pass through your existing validation and rate limiting; do not forward arbitrary client events or claimed purchase amounts directly to the tracker.

## 7. Connect official Roblox reports

Create an Open Cloud API key in Creator Hub for the relevant universe, granting `universe.analytics:read` in the `universe-analytics` system. In **Acquisition & discovery**, save the key. It is encrypted in the collector database using CONNECTOR_SECRET and is never returned by read endpoints.

The collector runs a five-minute scheduled task. Each pass processes bounded batches; a full metric cycle takes several passes. Multiple games share a fair rotation. Pending Roblox operations are stored and polled later. Failed imports retain the last stored results and display an error.

Standard metrics request up to 90 recent days; performance requests 27 days within Roblox's shorter retention. API availability, privacy thresholds and publication lag still apply. Daily qPTR is not live. The page preserves the API's values, breakdowns and status; native units are identified in Roblox's metric documentation. Official and custom retention are never blended.

Official references:

- https://create.roblox.com/docs/cloud/guides/analytics
- https://create.roblox.com/docs/cloud/guides/analytics/metrics
- https://create.roblox.com/docs/reference/engine/classes/MarketplaceService

## 8. Definitions and honest limits

- **D1/D7/D30:** a new first-observed cohort returns on the target UTC calendar date. A heartbeat across midnight counts as activity. Windows stay provisional until their target day ends. Late data can revise results.
- **Playtime:** observed session time through the last received observation, including active sessions; overlaps and reconnects within 60 seconds are merged. Missing leave events end at the last observation, not the timeout deadline. Long gaps between observations may overestimate continuous activity and are not evidence of a crash.
- **72-hour unique spenders:** distinct players with at least one recorded verified purchase in that rolling window. **Repeat spenders:** distinct players with two or more distinct purchases inside the same window. Daily unique counts are never added together.
- **Revenue:** directly observed Robux spent is not creator earnings, settled balance, DevEx value or net profit. Refund reconciliation is not inferred.
- **First purchase and repurchase timing:** based on retained history. Events before installation or before retention are unknown.
- **Cohort revenue:** cumulative observed spending in each player's first 7/30/90 days; incomplete windows are marked provisional.
- **Segments:** configurable observed inactivity and spending thresholds. Segment history uses retained sessions and can be incomplete for older players.
- **Suspicious activity:** heuristic flags for human review; no automatic bans or claims of proven cheating.
- **Player lookup:** uses the game's random pseudonym. For authorized support, server-only `Tracker.Pseudonym(player)` or `Tracker.LookupPseudonym(userId)` can find it. Never expose those helpers through an unrestricted RemoteFunction.
- **Release comparisons:** observational seven-day before/after windows. An unfinished after window is marked. Differences do not establish causation.
- **A/B tests:** stable 50/50 assignment from experiment ID plus player pseudonym. Call Tracker.Variant at actual exposure, then implement the returned control/treatment behaviour. A nil result means unavailable; use normal gameplay. The dashboard compares mature 72-hour payer conversion, excludes players observed in both variants, and shows sample counts and Wilson intervals. It does not declare a winner automatically.
- **Charts/reports:** saved charts show recorded series; PDF/CSV reports carry their own observation windows. Detailed lists paginate; totals are aggregated in the database rather than truncating to the first 20,000 events.

## 9. Alerts and data quality

Enable alerts in **Game settings & alerts**. Choose a stale-data timeout and a player-drop percentage. The collector checks them every five minutes. Player-drop checks require at least 20 observed players in the preceding five-minute window. Alerts are shown in Rover; email/Discord delivery is not configured.

A stale game may simply be empty. Check event freshness, last successful import, rejected batches, duplicate counts, and Tracker.Diagnostics(). Heartbeat diagnostics report queued/dropped events. No automatic system can prove 100% tracking coverage from successful requests alone.

Default ingest limit: 600 requests per game per minute, in batches of up to 100 events and 128 KB. Tune INGEST_REQUESTS_PER_MINUTE on the collector only after measuring actual traffic and database capacity. A 429 response triggers retries. The bounded game queue can lose data during a prolonged outage or shutdown; retries reuse event IDs and receipts deduplicate separately.

## 10. Backups, recovery and maintenance

Cloudflare D1 Time Travel provides platform-managed recovery according to your plan. Confirm the available recovery window in your own account. Scheduled collector cleanup defaults to 90 days and processes bounded batches. Player first/last-observed identifiers remain for cohort continuity; detailed historical records outside retention are not promised.

For an independent SQL backup, run these for BOTH databases:

```powershell
./scripts/backup.ps1 -DatabaseName rover-dashboard -ConfigPath work/cloudflare/wrangler.json
./scripts/backup.ps1 -DatabaseName rover-telemetry -ConfigPath collector/wrangler.jsonc
```

Use an authenticated machine or CI runner. Store exports encrypted in private storage, outside the website folder. They contain account hashes, audit records, pseudonymous player histories and encrypted API keys. Back up runtime secrets separately in your password manager. The backup script does not install a scheduler on your computer; schedule it on your server/CI if you want additional recurring exports.

Test recovery into a NEW EMPTY D1 database, never over the live one:

```powershell
./scripts/restore.ps1 -EmptyDatabaseName rover-recovery -ConfigPath YOUR_RECOVERY_CONFIG -BackupFile YOUR_PRIVATE_EXPORT.sql
```

Verify sample totals and account access, then deliberately switch the deployment binding to the recovered database. Database migration 0000 is preserved; 0001 adds accounts and analytics infrastructure. Never edit already-applied migrations.

## 11. Before trusting production numbers

1. Join a published test place and confirm production connection_test, join and heartbeat events.
2. Play across multiple heartbeat intervals and verify playtime advances without refreshing manually.
3. Rejoin and teleport between places; confirm the pseudonym stays stable and sessions do not inflate.
4. Test real purchase hooks using an appropriate test process and verify duplicate receipts count once.
5. Check a developer's assigned game and verify an unassigned game cannot be fetched or exported.
6. Rotate a test game's key and verify the previous one fails.
7. Disconnect/reconnect HTTP briefly and inspect retries, dropped events, and stale-data indicators.
8. Verify official Roblox API permissions and the first imported data points.
9. Check your first mature D1, then D7 cohort; neither can be finalized in advance.
10. Run a sustained live test and monitor billing, database size, requests and latency at your actual traffic.

Code tests and local HTTP tests are included. Roblox Studio execution, real credential integration, production load and sustained 24/7 collection still require your real game and hosting account.

## 12. Existing manual reports and scenarios

The original scenario calculator and observational notes remain under **Scenarios & notes**. Normalized report uploads remain under **Manual imports**. Use JSON containing `source`, `definition`, `unit`, `start`, `end`, and a `values` array. Manual uploads are stored separately and do not overwrite live or official metrics.
