Engagement features

This commit is contained in:
2026-10-21 13:10:25 -04:00
parent f4c10c9d7d
commit f9afa8ccbe
6 changed files with 405 additions and 4 deletions
+29
View File
@@ -31,6 +31,33 @@ The Chat section is a simple public chatroom. Logged-in players can post plain-t
Every successful button press also posts an automatic chat announcement with the player's name, exact time remaining, and points earned. These announcements are saved with the press and do not count toward the player's chat cooldown.
## Engagement roadmap
The goal is to make waiting social and consequential without rewarding button spam or changing the current game's rules. Planned delivery order:
1. **Optional live presence — implemented.** Show participating players watching the clock and who is viewing Chat. Off by default; ephemeral heartbeats, automatic expiry, and no activity history. See the details below.
2. **Personal bests and rivalries — implemented.** Your score shows closest call, highest-scoring press, leaderboard position, and the player immediately ahead with points needed to catch up. Existing history is credited automatically; no data migration is needed.
3. **Achievements.** Start with Close call (under a minute remaining), Full spectrum (every zone), and Keeping us alive (a meaningful quiet stretch; threshold to be decided). Credit existing history where possible. Avoid rewards for raw press volume or repeated resets.
4. **Infrequent community milestones.** Announce survival milestones, notable press totals, and new closest-call records in chat. Deduplicate announcements and keep them rare. Define thresholds and historic backfill behavior before implementation.
5. **Optional zone alerts.** Let players choose a zone and opt into sound or browser notifications. Request permissions only after an explicit action, alert on transitions rather than polls, and handle resets and denied permissions. Closed-browser delivery is separate work, not promised by basic tab notifications.
6. **Separate one-life mode — exploratory.** Allow one press per player per round in a separate mode with its own rounds and leaderboard. Never silently change the current game or reset its data.
Keep new features consistent with the Catppuccin Mocha styling and selectable-section navigation. Preserve visible countdown/button controls on desktop and mobile, authenticated mutations, bounded input, safe text rendering, and server-authoritative scoring. Verify changes using isolated databases rather than live game data.
### Personal bests and rivalries
Your score includes private personal records derived from your existing press history. Closest call means the least time remaining; highest-scoring press means the most points in a single press. Ties prefer the lower remaining time, then the earlier press. A zero-point press is still a valid record; new players see placeholders until their first press.
Leaderboard positions match the public ordering (points descending, then name alphabetically), not shared competition ranks. The rival is the player immediately above you, including alphabetical ties. The score card shows the gap to match their total and the minimum additional whole-number points to exceed it; actual presses still award fixed zone values, so you may jump past that threshold. Equal top scores are described as a shared lead; solo players and outright leaders receive their own messages. These cards refresh every five seconds while Your score is open and after pressing. Rival information exposes only public names and totals, never another player's records or press history. Achievements are the next roadmap step.
### Live presence
Logged-in players can enable **Show me online** in their score section. Presence is off by default. The count near the clock includes only opted-in players with a visible tab; Chat also lists opted-in players currently viewing that section. Multiple tabs/sessions count as one player. Updates run every 15 seconds, and stale presence expires after 45 seconds. Hiding a tab or opting out removes that tab's presence; logging out removes all presence for that session. Failed disconnect updates fall back to expiry. Presence lives only in server memory and is cleared on restart. Guests and players who leave the setting off are not tracked, so the count is not a total visitor count.
Public `GET /api/presence` returns `{online_count, chat_players}`. Authenticated `POST /api/presence` accepts `{tab_id, active, in_chat}` with a 32-character lowercase hexadecimal tab ID and boolean flags. At most 10 active tabs per login session are accepted. No player IDs, session IDs, or last-seen timestamps are returned publicly.
Presence tests cover authentication, validation, expiry and heartbeat renewal, tab/session deduplication, chat membership, revoked sessions, bounded entries, and logout. Browser checks cover opt-in/out, tab visibility changes, chat membership, logout, and responsive layout.
## Running
### Docker (recommended)
@@ -96,6 +123,8 @@ Tip: `BUTTON_DURATION_SECONDS=60` makes for quick testing.
| GET | `/api/leaderboard` | | Public player names, total points, and press counts |
| GET | `/api/chat` | | Latest 100 public chat messages |
| POST | `/api/chat` | ✓ | `{message}` → saved message; 200-character limit |
| GET | `/api/presence` | | Opted-in online count and chat display names |
| POST | `/api/presence` | ✓ | `{tab_id, active, in_chat}` presence heartbeat or removal |
| GET | `/api/stats` | | Global stats (403 until the game ends) |
| POST | `/api/subscribe` | ✓ | `{first_name, email, last_name?, phone?}` |