mirror of
https://github.com/williamsjack/pairwise-glicko-ranking.git
synced 2026-07-22 06:44:09 +00:00
Avoid building a full rank map for pair updates
This commit is contained in:
parent
32f5618366
commit
dd080a3488
6 changed files with 47 additions and 13 deletions
|
|
@ -8,7 +8,7 @@ import type { PluginDataStore } from '../storage/PluginDataStore';
|
|||
import { ConfirmModal } from '../ui/ConfirmModal';
|
||||
import { ALL_SENTINEL, ResetNoteModal } from '../ui/ResetNoteModal';
|
||||
import {
|
||||
computeRankMap,
|
||||
computeRanksForAll,
|
||||
updateCohortFrontmatter,
|
||||
writeFrontmatterStatsForPlayer,
|
||||
} from '../utils/FrontmatterStats';
|
||||
|
|
@ -88,7 +88,7 @@ export async function resetNoteRating(
|
|||
def.frontmatterOverrides,
|
||||
);
|
||||
|
||||
const rankMap = computeRankMap(cohort);
|
||||
const rankMap = computeRanksForAll(cohort);
|
||||
|
||||
// Write the reset note's own stats (rating, matches, wins, rank)
|
||||
await writeFrontmatterStatsForPlayer(app, fm, cohort, file, noteId, rankMap);
|
||||
|
|
|
|||
|
|
@ -189,7 +189,9 @@ export function computePlaceholders(
|
|||
const sessionWithSigma = allWithSigma.filter(({ id }) => uniquePlayerIds.has(id));
|
||||
|
||||
const formatStabilityList = (entries: { id: string; sigma: number }[]) =>
|
||||
entries.map((e) => `- ${idToName(e.id, data.idToPath)} (σ ${Math.round(e.sigma)})`).join('\n');
|
||||
entries
|
||||
.map((e) => `- ${idToName(e.id, data.idToPath)} (σ ${Math.round(e.sigma)})`)
|
||||
.join('\n');
|
||||
|
||||
// Session participants only
|
||||
const byStabilitySession = sessionWithSigma.slice().sort((a, b) => a.sigma - b.sigma);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { CohortPicker } from './ui/CohortPicker';
|
|||
import { ensureBaseCohortTarget } from './utils/EnsureBaseCohort';
|
||||
import { ensureFolderCohortPath } from './utils/EnsureFolderCohort';
|
||||
import { ensureUniqueIds } from './utils/EnsureUniqueIds';
|
||||
import { computeRankMap, updateCohortFrontmatter } from './utils/FrontmatterStats';
|
||||
import { computeRanksForAll, updateCohortFrontmatter } from './utils/FrontmatterStats';
|
||||
import { debugWarn, setDebugLogging } from './utils/logger';
|
||||
|
||||
export default class GlickoPlugin extends Plugin {
|
||||
|
|
@ -262,7 +262,7 @@ export default class GlickoPlugin extends Plugin {
|
|||
});
|
||||
if (files.length === 0) return;
|
||||
|
||||
const rankMap = computeRankMap(cohort);
|
||||
const rankMap = computeRanksForAll(cohort);
|
||||
|
||||
updateCohortFrontmatter(
|
||||
this.app,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { ConfirmModal } from '../ui/ConfirmModal';
|
|||
import { FolderSelectModal } from '../ui/FolderPicker';
|
||||
import { FM_PROP_KEYS, renderStandardFmPropertyRow } from '../ui/FrontmatterPropertyRow';
|
||||
import {
|
||||
computeRankMap,
|
||||
computeRanksForAll,
|
||||
previewCohortFrontmatterPropertyUpdates,
|
||||
updateCohortFrontmatter,
|
||||
} from '../utils/FrontmatterStats';
|
||||
|
|
@ -562,7 +562,7 @@ export default class GlickoSettingsTab extends PluginSettingTab {
|
|||
const map = new Map<string, number>();
|
||||
if (!cohort) return map;
|
||||
if (key === 'rank') {
|
||||
const rankMap = computeRankMap(cohort);
|
||||
const rankMap = computeRanksForAll(cohort);
|
||||
for (const [id, rank] of rankMap) map.set(id, rank);
|
||||
} else if (key === 'rating') {
|
||||
for (const [id, p] of Object.entries(cohort.players)) map.set(id, Math.round(p.rating));
|
||||
|
|
|
|||
|
|
@ -214,7 +214,11 @@ export class PluginDataStore {
|
|||
ts: now,
|
||||
};
|
||||
|
||||
// Inflate sigma for staleness (Glicko-1 RD inflation)
|
||||
// Inflate sigma for staleness (Glicko-1 RD inflation).
|
||||
// Cap is intentionally stabilityThreshold, not DEFAULT_SIGMA: notes don't
|
||||
// "lose skill" like human players, so we only allow modest RD inflation to
|
||||
// accommodate preference drift. This also prevents the stability progress
|
||||
// bar from regressing after long gaps between sessions.
|
||||
const cap = this.settings.stabilityThreshold ?? 150;
|
||||
const preSigmaA = inflateSigma(a.sigma ?? DEFAULT_SIGMA, now - (a.lastMatchAt ?? now), cap);
|
||||
const preSigmaB = inflateSigma(b.sigma ?? DEFAULT_SIGMA, now - (b.lastMatchAt ?? now), cap);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function anyEnabled(fm: FrontmatterPropertiesSettings): boolean {
|
|||
}
|
||||
|
||||
// Standard competition ranking ("1224" style)
|
||||
export function computeRankMap(cohort: CohortData): Map<string, number> {
|
||||
export function computeRanksForAll(cohort: CohortData): Map<string, number> {
|
||||
const entries = Object.entries(cohort.players);
|
||||
entries.sort((a, b) => b[1].rating - a[1].rating);
|
||||
|
||||
|
|
@ -38,6 +38,33 @@ export function computeRankMap(cohort: CohortData): Map<string, number> {
|
|||
return map;
|
||||
}
|
||||
|
||||
// Compute ranks for a subset of players by counting how many players
|
||||
// have a strictly higher rating (standard competition ranking)
|
||||
function computeRanksForSubset(cohort: CohortData, playerIds: string[]): Map<string, number> {
|
||||
const targets = playerIds
|
||||
.map((id) => ({ id, rating: cohort.players[id]?.rating }))
|
||||
.filter((t): t is { id: string; rating: number } => t.rating !== undefined);
|
||||
|
||||
if (targets.length === 0) return new Map();
|
||||
|
||||
// For each player in the cohort, check if their rating exceeds
|
||||
// any target's rating and increment that target's rank counter
|
||||
const higherCounts = new Map<string, number>(targets.map((t) => [t.id, 0]));
|
||||
for (const player of Object.values(cohort.players)) {
|
||||
for (const t of targets) {
|
||||
if (player.rating > t.rating) {
|
||||
higherCounts.set(t.id, higherCounts.get(t.id)! + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rankMap = new Map<string, number>();
|
||||
for (const [id, count] of higherCounts) {
|
||||
rankMap.set(id, count + 1);
|
||||
}
|
||||
return rankMap;
|
||||
}
|
||||
|
||||
function buildProps(fm: FrontmatterPropertiesSettings, stats: PlayerStats): Record<string, number> {
|
||||
const out: Record<string, number> = {};
|
||||
|
||||
|
|
@ -78,7 +105,8 @@ export async function writeFrontmatterStatsForPair(
|
|||
if (!cohort) return;
|
||||
if (!anyEnabled(fm)) return;
|
||||
|
||||
const rankMap = computeRankMap(cohort);
|
||||
const ids = [aId, bId].filter((id): id is string => !!id);
|
||||
const rankMap = computeRanksForSubset(cohort, ids);
|
||||
const tasks: Promise<void>[] = [];
|
||||
|
||||
if (aFile && aId) {
|
||||
|
|
@ -258,7 +286,7 @@ export async function updateCohortRanksInFrontmatter(
|
|||
newPropName: string,
|
||||
): Promise<{ updated: number }> {
|
||||
if (!cohort) return { updated: 0 };
|
||||
const rankMap = computeRankMap(cohort);
|
||||
const rankMap = computeRanksForAll(cohort);
|
||||
return updateCohortFrontmatterProperties(app, files, rankMap, newPropName);
|
||||
}
|
||||
|
||||
|
|
@ -268,12 +296,12 @@ export async function writeFrontmatterStatsForPlayer(
|
|||
cohort: CohortData,
|
||||
file: TFile,
|
||||
playerId: string,
|
||||
precomputedRankMap?: Map<string, number>,
|
||||
precomputedRankMap: Map<string, number>,
|
||||
): Promise<void> {
|
||||
if (!anyEnabled(fm)) return;
|
||||
const p = cohort.players[playerId];
|
||||
if (!p) return;
|
||||
const rankMap = precomputedRankMap ?? computeRankMap(cohort);
|
||||
const rankMap = precomputedRankMap;
|
||||
const props = buildProps(fm, {
|
||||
rating: p.rating,
|
||||
matches: p.matches,
|
||||
|
|
|
|||
Loading…
Reference in a new issue