Files
modbison/docs/planning/discovery.md

6.7 KiB
Raw Permalink Blame History

Discovery — Browse, Search & Home — Plan

Status: Planned Owner: Platform team Feature ID: discovery Godot 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/worlds requires auth and returns only PUBLIC worlds or your own worlds, ordered by created_at only).
  • 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 / CurrentUser dependency in app/dependencies.py. Discovery listing must work both for anonymous visitors and authenticated users, so it needs an optional auth dependency (not CurrentUser, 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.py already 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.

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

  • category filter matches exactly (case-insensitive).
  • Distinct category list is available for UI dropdowns.
  • newest, most_played, and trending sorts are supported.
  • most_played uses play_count; trending uses 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}/visit increments play_count and updates last_played_at.
  • Appends a world_visits row (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_visits is append-only signal, not a BI surface.