Feature Requests
Planned

[G1] XP ledger, scoring model and level curve (foundation)

Foundation ticket for the whole gamification effort. Everything else plugs into the point economy defined here, so this lands first. No UI in this ticket.

Why first

XP is the currency behind levels, the leaderboard and part of the challenge system. Defining the scale late would mean recalibrating scores that are already public, which is unmanageable. The scale is settled here, then reused.

Scope

  • XpEntry ledger, append-only like ActivityEvent. Reversal of a cancelled action is a negative entry referencing the original, never a delete.
  • Mandatory reason + sourceType / sourceId pointing at the real entity. Uniqueness on (userId, reason, sourceType, sourceId). For repeatable gains, the natural source is the event row (EpisodeWatch, Comment), which is already unique — so a rewatch legitimately earns again while an API retry is rejected. Watch out: Postgres treats NULL as distinct in a unique index, so gains with no source entity escape the constraint.
  • Materialised counter on User (sum of the ledger), needed for leaderboard performance. The ledger stays the source of truth.
  • Daily caps computed from the ledger, in the user's own timezone (User.timezone), not UTC.
  • Level curve as a pure function in packages/shared. The level is never stored — it derives from XP. Two knobs only: base cost B and exponent p, cost of level N→N+1 = B × N^p. Starting point to challenge: B=100, p=1.5 (level 2 during onboarding, ~10 after nine months of regular use, 50 out of practical reach). No maximum level. Recalibrating must never require a migration.
  • Initial scoring table. Indicative starting point, to be argued during implementation: episode 10 · movie 50 · book finished 150 · game finished 200 · album 20 · series finished 100 · review 50 (+30 when written, above a minimum word count) · comment 10 (max 5/day, minimum trimmed length ~15 chars) · helpful vote received 5 · achievement 100–1000 by rarity.
  • GAMIFICATION_ENABLED flag, same role as SOCIAL_ENABLED: a self-host choice and a kill switch, not a rollout flag.

Settled decisions

  • One single global level. Social is one XP source among others, not a separate level.
  • XP, levels and achievements work without SOCIAL_ENABLED. Only the leaderboard and social-sourced XP are gated.
  • Imports never grant XP (they do grant achievements — see G2/G3). XP must stay comparable across accounts, and importing ten years of Trakt is comparable to nothing.
  • XP is taken back when the underlying action is undone, otherwise an add/remove loop is a farming exploit.

Out of scope

Any UI, achievements, the leaderboard, penalties (see B8).

Open questions for implementation

  • Final shape of the scoring table and the curve constants.
  • Which reasons need a source entity to make the unique index effective.

0 Comments

Sign in to comment

No comments yet. Be the first to share your thoughts!