6.7 KiB
Discovery — Browse, Search & Home — Plan
Status: Planned Owner: Platform team Feature ID:
discoveryGodot version: 4.6.2 stable Backend: FastAPI + SQLAlchemy (async) + PostgreSQL + Redis
1. Overview
This document breaks the Discovery feature into user stories and right-sized implementation tasks. The feature delivers:
- Home / discover page — a default listing of experiences a player lands on.
- Search — find worlds by name and by tag.
- Categories — a single category per world plus free-form tags, with filtering.
- Simple sorting — newest, most-played, and trending. Trending starts as a basic recent-activity heuristic (weighted recent visits); no real recommendation model for MVP.
Without this, nothing else built (experiences, creator content) is actually reachable by a real user. Discovery is the front door.
2. Why this matters
The platform's value is reachable content. Worlds and creator assets already
exist in the data model (World, Asset), but there is currently:
- No public listing of worlds (
/api/worldsrequires auth and returns only PUBLIC worlds or your own worlds, ordered bycreated_atonly). - No search by name or tag.
- No category/tag metadata on worlds at all.
- No play-count or recency signal to power "most-played" or "trending".
- No client UI for browsing.
Discovery closes that gap with a small, dependency-free metadata layer plus a read-optimized listing endpoint.
3. Existing constraints (do not re-build)
- Auth: JWT bearer tokens;
get_current_user/CurrentUserdependency inapp/dependencies.py. Discovery listing must work both for anonymous visitors and authenticated users, so it needs an optional auth dependency (notCurrentUser, which returns 401). - DB: async SQLAlchemy + Alembic; migration head is
0002_add_date_of_birth_to_users. - Models:
World(id, name, description, owner_id, visibility, created_at, updated_at).User,Asset(owner_id, is_public) exist. No tags/categories/ play-count fields yet. - Routers:
worlds.pyalready owns CRUD under/api/worlds. Discovery adds a new read-focused router rather than overloading it. - Redis: configured (
REDIS_URL) but unused; not required for discovery MVP (Postgres queries are sufficient at this scale). - Client: Godot 4.6.2, minimal
main_menu.gd; no networking client, no discovery UI.
4. Key design decisions
| Concern | Decision |
|---|---|
| Listing transport | REST (GET /api/discovery/worlds) — no WebSocket needed for browse |
| Anonymous browsing | Listing/read endpoints accept optional auth; visibility filtering treats anonymous users as "PUBLIC only" |
| Categories | Single category string column on worlds (indexed). Free-form for MVP; a distinct-categories list is derived with a SELECT DISTINCT |
| Tags | PostgreSQL ARRAY(String) column on worlds with a GIN index for @> tag containment search |
| Play count | play_count integer (default 0) on worlds, incremented atomically on visit |
| Recency signal | last_played_at timestamp on worlds + append-only world_visits table for the trending heuristic |
| Trending (MVP) | Weighted score over recent visits: sum(decay_weight(age)) over the last N days, no ML |
| Sorting | newest (created_at desc), most_played (play_count desc), trending (score desc) |
| Pagination | skip/limit (limit capped at 100), consistent with existing routers |
| Visibility | Discovery only surfaces PUBLIC worlds; never leaks private/friends_only worlds |
5. User stories
US-1 Home / discover feed
As a player, I can open a home/discover page and see a default list of experiences without searching.
Acceptance criteria
- Returns PUBLIC worlds only, newest-first by default.
- Paginated with a stable total count.
- Works anonymously and authenticated.
- Each result includes id, name, description, category, tags, owner, play_count, and created_at.
US-2 Search
As a player, I can search experiences by name and by tag.
Acceptance criteria
- Case-insensitive substring search on
name. - Tag search matches worlds containing any of the requested tags.
- Search combines with category filter and sort.
US-3 Categories & sorting
As a player, I can filter by category and sort by newest, most-played, or trending.
Acceptance criteria
categoryfilter matches exactly (case-insensitive).- Distinct category list is available for UI dropdowns.
newest,most_played, andtrendingsorts are supported.most_playedusesplay_count;trendinguses the recent-visits heuristic.
US-4 Visit tracking
As a player, opening a world records a visit so counts and trending stay fresh.
Acceptance criteria
POST /api/worlds/{id}/visitincrementsplay_countand updateslast_played_at.- Appends a
world_visitsrow (world_id, user_id or anonymous marker, visited_at). - Idempotent per request; rate-limitable later.
US-5 Godot client
As a player, I can browse, search, filter, and sort experiences from the game UI.
Acceptance criteria
- An HTTP API client handles the discovery endpoints (JWT + anonymous).
- A discover/home screen lists experiences with search, category filter, and sort controls.
- Results render name, category, tags, play count, and description.
6. Task breakdown
Right-sized to 1–3 files / one coherent piece of functionality each.
| # | Task | Story | Depends on |
|---|---|---|---|
| 1 | World discovery metadata + visits model & Alembic migration | US-2, US-3, US-4 | — |
| 2 | World visit tracking endpoint (play count + last played + visit log) | US-4 | 1 |
| 3 | Browse + search endpoint (name/tag/category, newest + most-played) | US-1, US-2, US-3 | 1, 2 |
| 4 | Trending sort (recent-activity heuristic over world_visits) |
US-3 | 3 |
| 5 | Godot discovery API client (HTTP + optional JWT) | US-5 | 3 |
| 6 | Godot discover/home UI (feed, search, category filter, sort) | US-5 | 4, 5 |
7. Phasing
- Phase 1 (data + core read API): tasks 1–3 (metadata, visit tracking, browse/search).
- Phase 2 (trending): task 4 — the only non-trivial query; isolated so it can be tuned without touching the browse surface.
- Phase 3 (client): tasks 5–6, each landing after its backend dependency.
8. Out of scope for this feature
- Real recommendation models / personalization (collaborative filtering, ML).
- Full-text search (Postgres FTS); MVP uses ILIKE substring match.
- Faceted search with multiple simultaneous category/tag AND-semantics.
- Featured/editorial curation and admin-curated carousels.
- Search result relevance scoring beyond the three explicit sorts.
- Analytics dashboards;
world_visitsis append-only signal, not a BI surface.