Files
vngeoguessr/docs/game-flow.md
T
tiennm99 67b9195d63 feat(username): prompt for a name on landing
Open the username modal on first visit when localStorage holds no name,
instead of waiting for the first Play click. Dismissing the prompt with
no saved name falls back to a generated Player-xxxxxx, so every exit
path leaves a leaderboard name behind.
2026-09-02 01:57:53 +07:00

5.3 KiB
Raw Blame History

Gameplay Flow

Complete Game Flow

1. Username Setup

  • Check localStorage for existing username
  • When no name is stored, UsernameModal opens on landing to suggest picking one; it also opens at a Play click if a name is somehow still missing, and navigation resumes after the name is settled
  • Skip, dismissing the prompt (Esc / overlay click), or a deep link straight into /game generates a random Player-xxxxxx name and stores it like a typed one
  • The header chip ("Playing as X" / "Set name") reopens the modal to change the name at any time
  • Store username for leaderboard tracking

2. Region Selection

  • Choose anywhere on the region tree:
    • Vietnam — draws from every covered province
    • A province — Ha Noi, Ho Chi Minh, Da Nang, Lam Dong, Long An
    • A district or town — 61 of them, expanded from their province
  • Regions with no usable panoramas are listed but disabled, with the reason shown. See the Coverage note in project-overview.md

3. Session Creation

  • Server generates unique UUID v4 session ID
  • Server stores the exact target location and the district the panorama sits in in Redis with 30-minute expiry
  • Client receives session ID only — never the coordinates, never the district

4. Location Display Process

  • Pick from the index: the server draws a random panorama id from the prebuilt index in Postgres for the selected region (pickRandomPano in src/lib/pano-index.js). A country draw picks a province uniformly first, so the round is not dominated by whichever province has the most panoramas
  • Resolve the image: one Mapillary lookup by id, ~230ms. Up to three candidates are tried in case an image was deleted upstream
  • Credit the district that won: the resolving district comes from the attempt that succeeded, not the first candidate — each retry may sit in a different district
  • Image Display: thumb_2048_url, falling back to thumb_original_url
  • Security: client never receives the coordinates or the district

5. Guessing Phase

  • Image Interaction: View street-level thumbnail image
  • Map Interaction: Place guess marker on interactive Leaflet map with OpenStreetMap
  • Map Search: optional search for a district, street, or place to pan the map before clicking; results never reveal or place the answer
  • Submission: Click submit button to finalize guess

6. Server-Side Processing

  • Input: Client submits guess coordinates + session ID only
  • Retrieval: Server retrieves exact location from Redis session storage
  • Validation: Server validates coordinate ranges and session existence
  • Calculation: Server calculates distance using Turf.js distance function
  • Scoring: Server-side score calculation with distance-based points
  • Cleanup: the session is claimed with an atomic DEL before any score is written, so a replay or a concurrent submit scores exactly once

7. Scoring System

Distance-based points (0-5 scale). The headline score is graded against a ladder scaled to the size of the region the player picked; base ladder (exact only for regions at or under the 10km reference diagonal):

  • 0-50m: 5 points
  • 50m-100m: 4 points
  • 100m-200m: 3 points
  • 200m-500m: 2 points
  • 500m-1km: 1 point
  • beyond: 0 points

A larger region multiplies every threshold by its bbox diagonal over the 10km reference (see bandsForBbox in src/lib/game.js). Leaderboards are credited separately: each board grades the raw distance against its own region's ladder (submitRoundScore in src/lib/leaderboard.js), so the headline score and a board's added points can differ.

8. Results Display

  • Show calculated distance between guess and actual location
  • Display earned points for current round (0-5 scale)
  • Reveal the region path the panorama was in, e.g. Vietnam › Ho Chi Minh › District 7 — this is the first time the client learns the district
  • Show the accumulated total and rank at each of the three levels
  • Show distance leaderboard rankings for the current game at each level
  • Reveal exact target coordinates and compare guess vs actual on the map

9. Leaderboard Management

  • Rollup fan-out: each game updates the score board and the distance board at every level above the panorama — normally district, province and Vietnam, or province and Vietnam when the panorama fell outside every district outline. The levels are written concurrently
  • Score Leaderboards: Accumulated scoring system with single entry per user
  • Distance Leaderboards: Best distance records with multiple entries per user allowed
  • Redis Sorted Sets: Persistent storage using ZADD/ZRANGE operations
  • Top 200 Limit: Automatic trimming per leaderboard to maintain top performers only
  • Score Accumulation: New scores added to existing totals in score leaderboards
  • Distance Records: Each game creates new timestamped distance record entry
  • Real-time Ranking: Dynamic rank calculation using ZREVRANK/ZRANK for all leaderboard types
  • Persistent Storage: No expiration on leaderboard data

10. Continue or Exit

  • Option to start next round with new session and location in the same region
  • Option to return to the region picker and choose somewhere else
  • Option to view full leaderboard with pagination
  • Redis session cleanup ensures fresh start for each round