···11-# PVZM Backend 
11+# PVZM Backend 
2233> A Deno-powered backend service for [Plants vs. Zombies: MODDED](https://github.com/roblnet13/pvz). This service provides APIs for uploading, downloading, listing, favoriting, and reporting user-created _I, Zombie_ levels.
44
+64-4
modules/routes/levels.ts
···293293 const orderDirection = reversedOrder ? "ASC" : "DESC";
294294295295 let orderClause: string;
296296+ let useDiversitySort = false;
296297 if (sort === "featured") {
297298 // Check if database has mature engagement data (any level with 100+ plays)
298299 const maxPlaysResult = dbCtx.db.prepare("SELECT MAX(plays) as max_plays FROM levels").get() as { max_plays: number } | undefined;
···302303 if (hasMatureData) {
303304 // Balanced approach: recency + quality
304305 // Recency weight: 1 point per day since epoch, quality: favorites * 100 + plays
305305- orderClause = `featured DESC, (created_at / 86400.0 + favorites * 100 + plays) DESC`;
306306+ orderClause = `(created_at / 86400.0 + favorites * 100 + plays) DESC`;
306307 } else {
307308 // New database: heavily favor recency with minimal quality impact
308309 // Recency weight: 1 point per day, quality: favorites * 10 + plays / 10
309310 // This makes recency ~100x more important than in the mature formula
310310- orderClause = `featured DESC, (created_at / 86400.0 + favorites * 10 + plays / 10.0) DESC`;
311311+ orderClause = `(created_at / 86400.0 + favorites * 10 + plays / 10.0) DESC`;
311312 }
313313+ useDiversitySort = true;
312314 } else {
313315 const orderColumn = sort === "recent" ? "created_at" : sort === "favorites" ? "favorites" : "plays";
314316 orderClause = `${orderColumn} ${orderDirection}, id ${orderDirection}`;
···338340 params.push(version);
339341 }
340342343343+ // Featured sort only shows featured levels
344344+ if (tokenLevelId === null && sort === "featured") {
345345+ filters.push("featured = ?");
346346+ params.push(1);
347347+ }
348348+341349 let query = `SELECT id, name, author, created_at, sun, is_water, favorites, plays, difficulty, version, featured, featured_at FROM levels`;
342350343351 if (filters.length > 0) {
344352 query += " WHERE " + filters.join(" AND ");
345353 }
354354+355355+ // For featured sort with diversity, fetch more results to allow for re-ranking
356356+ const shouldApplyDiversity = useDiversitySort && tokenLevelId === null;
357357+ const fetchLimit = shouldApplyDiversity ? limit * 3 : limit;
358358+ const fetchOffset = shouldApplyDiversity ? offset : offset;
346359347360 query += ` ORDER BY ${orderClause} LIMIT ? OFFSET ?`;
348348- params.push(limit, offset);
361361+ params.push(fetchLimit, fetchOffset);
362362+363363+ let levels = dbCtx.db.prepare(query).all(...params);
349364350350- const levels = dbCtx.db.prepare(query).all(...params);
365365+ // Apply author diversity algorithm for featured sort
366366+ if (shouldApplyDiversity && Array.isArray(levels) && levels.length > 0) {
367367+ type LevelWithScore = {
368368+ id: number;
369369+ author: string;
370370+ created_at: number;
371371+ favorites: number;
372372+ plays: number;
373373+ featured: number;
374374+ score: number;
375375+ [key: string]: unknown;
376376+ };
377377+378378+ const authorCounts = new Map<string, number>();
379379+ const maxPlaysResult = dbCtx.db.prepare("SELECT MAX(plays) as max_plays FROM levels").get() as { max_plays: number } | undefined;
380380+ const hasMatureData = (maxPlaysResult?.max_plays ?? 0) >= 100;
381381+382382+ // Calculate scores and apply diversity penalties
383383+ const levelsWithScores = (levels as LevelWithScore[]).map(level => {
384384+ // Calculate base score (same formula as SQL)
385385+ let baseScore: number;
386386+ if (hasMatureData) {
387387+ baseScore = level.created_at / 86400.0 + level.favorites * 100 + level.plays;
388388+ } else {
389389+ baseScore = level.created_at / 86400.0 + level.favorites * 10 + level.plays / 10.0;
390390+ }
391391+392392+ // Apply diversity penalty
393393+ const authorCount = authorCounts.get(level.author) || 0;
394394+ authorCounts.set(level.author, authorCount + 1);
395395+396396+ // Penalty increases exponentially: 0, -500, -1500, -3500, -7500...
397397+ const diversityPenalty = authorCount === 0 ? 0 : -500 * (Math.pow(2, authorCount) - 1);
398398+399399+ return {
400400+ ...level,
401401+ score: baseScore + diversityPenalty
402402+ };
403403+ });
404404+405405+ // Re-sort by adjusted scores
406406+ levelsWithScores.sort((a, b) => b.score - a.score);
407407+408408+ // Take only the requested limit
409409+ levels = levelsWithScores.slice(0, limit);
410410+ }
351411352412 type LevelRow = {
353413 id: number;