Skip to main content

XP and Leveling

Experience points (XP) are the core currency of progression in CodeJam. Earn XP by completing challenges, quests, and daily activities.

User Schema

Your progression data is stored in the users table:
convex/schema.ts
users: defineTable({
  // ... auth fields
  xp: v.optional(v.number()),
  level: v.optional(v.number()),
  questsCompleted: v.optional(v.number()),
  streak: v.optional(v.number()),
  lastActiveDay: v.optional(v.string()), // YYYY-MM-DD
  lastSeen: v.optional(v.number()), // Timestamp for presence
  preferredLanguage: v.optional(v.string()),
}).index("email", ["email"]).index("by_xp", ["xp"])
The by_xp index enables efficient leaderboard queries and rank calculations.

Earning XP

XP is awarded through various activities tracked by the activity log system.

Activity Logging

Every XP-earning action is recorded:
convex/activity.ts
export const logActivity = mutation({
  args: {
    type: v.string(),
    xp: v.number(),
  },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");
    
    await ctx.db.insert("activity_logs", {
      userId,
      type: args.type,
      xp: args.xp,
      timestamp: Date.now(),
    });

    const user = await ctx.db.get(userId);
    if (user) {
      const today = new Date().toISOString().split('T')[0];
      const yesterdayDate = new Date();
      yesterdayDate.setDate(yesterdayDate.getDate() - 1);
      const yesterday = yesterdayDate.toISOString().split('T')[0];

      let newStreak = user.streak || 0;

      // Streak logic
      if (user.lastActiveDay === yesterday) {
        newStreak += 1;
      } else if (user.lastActiveDay !== today) {
        newStreak = 1; // Reset or start new
      }

      await ctx.db.patch(userId, {
        xp: (user.xp || 0) + args.xp,
        questsCompleted: (user.questsCompleted || 0) + 1,
        level: Math.floor(((user.xp || 0) + args.xp) / 1000) + 1,
        streak: newStreak,
        lastActiveDay: today
      });
    }
  }
});

Level Formula

Your level is calculated automatically:
level = Math.floor(totalXP / 1000) + 1
Every 1000 XP grants one level. There is no level cap!

XP Sources

Base XP varies by difficulty:
  • Beginner: 100 XP (Syntax Smasher)
  • Intermediate: 150-175 XP (Function Fury, Logic Labyrinth)
  • Advanced: 200 XP (CSS Combat)
  • Expert: 300 XP (Algo Arena)
Custom XP rewards per quest
  • Tracked via questsCompleted counter
  • Increments with each activity log
  • Contributes to overall progression
Bonus XP for consistent engagement
  • First activity each day maintains streak
  • Streak bonuses unlock badges
  • Weekly XP charts track daily gains

Streak System

Streaks reward daily consistency and are central to progression.

How Streaks Work

The lastActiveDay field tracks your last activity date:
1

First Activity

When you earn XP, the system checks your lastActiveDay
2

Streak Calculation

if (lastActiveDay === yesterday) {
  newStreak = currentStreak + 1; // Continue streak
} else if (lastActiveDay !== today) {
  newStreak = 1; // Reset or start new
}
// If lastActiveDay === today, streak unchanged
3

Update User

Streak and lastActiveDay are updated atomically with XP gains
Streaks reset if you miss a day! Activity must occur on consecutive calendar days (UTC-based).

Streak Scenarios

// Yesterday: 2024-03-15
// Today: 2024-03-16
// lastActiveDay: "2024-03-15"
// Result: streak += 1

Streak Milestones

Reaching streak milestones unlocks special badges:

5 Day Streak

Unlock the 5 Day Streak badgeBadge ID: streak-5

Custom Streaks

Additional streak badges availableCheck the Achievements page

Weekly XP Tracking

The getWeeklyXP query powers your activity chart:
convex/activity.ts
export const getWeeklyXP = query({
  args: {},
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) return [];

    // Get logs for this user
    const logs = await ctx.db
      .query("activity_logs")
      .withIndex("by_user", (q) => q.eq("userId", userId))
      .order("desc")
      .take(100); 

    const dailyXP = new Map<string, number>();
    const now = new Date();
    
    // Initialize last 7 days
    for (let i = 6; i >= 0; i--) {
      const d = new Date(now);
      d.setDate(d.getDate() - i);
      const key = d.toISOString().split('T')[0];
      dailyXP.set(key, 0);
    }

    // Aggregate XP by day
    logs.forEach(log => {
      const date = new Date(log.timestamp).toISOString().split('T')[0];
      if (dailyXP.has(date)) {
        dailyXP.set(date, (dailyXP.get(date) || 0) + log.xp);
      }
    });

    return Array.from(dailyXP.values());
  },
});
This query returns an array of 7 numbers representing XP earned each day for the past week.

Activity Log Schema

All progression events are recorded:
convex/schema.ts
activity_logs: defineTable({
  userId: v.id("users"),
  type: v.string(), 
  xp: v.number(),
  timestamp: v.number(),
}).index("by_user", ["userId"])

Activity Types

The type field categorizes different activities:
  • Challenge completion: e.g., "syntax-smasher", "css-combat"
  • Quest completion: e.g., "quest-completed"
  • Special events: e.g., "daily-bonus", "streak-milestone"
Activity logs are queryable by user via the by_user index for efficient personal history retrieval.

Quest Completion

The questsCompleted field tracks total quests finished:
Activity Logging
questsCompleted: (user.questsCompleted || 0) + 1
Currently, every activity log increments questsCompleted. This may be refined to specific quest types in future updates.

Presence Tracking

The lastSeen timestamp tracks real-time presence:
convex/users.ts
export const heartbeat = mutation({
  args: {},
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) return;
    await ctx.db.patch(userId, { lastSeen: Date.now() });
  },
});
This enables features like:
  • Friend online status
  • Activity feeds
  • Recent player lists

Progression Dashboard

Your dashboard displays comprehensive progression stats:

Total XP

Cumulative experience points earned

Current Level

Calculated from total XP

Active Streak

Consecutive days active

Quests Completed

Total completed activities

Weekly XP Chart

Last 7 days of activity

Recent Badges

Latest 3 unlocked achievements

Progression Tips

1

Play Daily

Even a single challenge maintains your streak and earns XP
2

Target High XP Challenges

Expert challenges (300 XP) level you up 3x faster than beginner (100 XP)
3

Track Weekly Progress

Use the weekly XP chart to identify patterns and stay consistent
4

Complete All Objectives

Full objective completion maximizes XP rewards
XP and levels are permanent, but streaks can reset. Protect your streak by playing daily!

Next Steps

Challenges

Learn about challenge types and objectives

Achievements

Unlock badges and track milestones

Leaderboard

See how your XP compares globally

Users API

API reference for user stats and activity tracking

Build docs developers (and LLMs) love