@orakl/shared 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/badges.d.ts +6 -3
- package/dist/badges.js +51 -19
- package/package.json +1 -1
package/dist/badges.d.ts
CHANGED
|
@@ -37,12 +37,14 @@ export type BadgeContext = {
|
|
|
37
37
|
/** Rounds this player alone was right with the field answering (Lone torch). */
|
|
38
38
|
soleCorrectRounds?: number;
|
|
39
39
|
/**
|
|
40
|
-
* This player's rank after each closed round, with the field size then
|
|
41
|
-
*
|
|
40
|
+
* This player's rank after each closed round, with the field size then
|
|
41
|
+
* and whether their score was strictly above everyone else's; null for
|
|
42
|
+
* rounds before they held a hero slot (Ascendant, Undisputed).
|
|
42
43
|
*/
|
|
43
44
|
rankHistory?: Array<{
|
|
44
45
|
rank: number;
|
|
45
46
|
field: number;
|
|
47
|
+
lead: boolean;
|
|
46
48
|
} | null>;
|
|
47
49
|
};
|
|
48
50
|
export interface BadgeInput {
|
|
@@ -110,7 +112,8 @@ export interface FieldLogEntry {
|
|
|
110
112
|
/**
|
|
111
113
|
* Field-relative round counts for `playerId` from every tenured player's log.
|
|
112
114
|
* The field for a round is the other players' submitted answers; a miss-filled
|
|
113
|
-
* row (a leaver, or a timeout) is not a wrong answer and is left out.
|
|
115
|
+
* row (a leaver, or a timeout) is not a wrong answer and is left out. Round
|
|
116
|
+
* tallies are cached per `logs` map, so `logs` must not change between calls.
|
|
114
117
|
*/
|
|
115
118
|
export declare function fieldRoundStats(playerId: string, logs: ReadonlyMap<string, readonly FieldLogEntry[]>): {
|
|
116
119
|
fieldRoundsDefied: number;
|
package/dist/badges.js
CHANGED
|
@@ -167,7 +167,7 @@ export const BADGES = {
|
|
|
167
167
|
"master-of-none": {
|
|
168
168
|
id: "master-of-none",
|
|
169
169
|
label: "Many-sided",
|
|
170
|
-
description: "Earned
|
|
170
|
+
description: "Earned five other badges in one run spanning four or more categories.",
|
|
171
171
|
icon: "dice",
|
|
172
172
|
modes: CROSS_MODE,
|
|
173
173
|
},
|
|
@@ -302,7 +302,7 @@ const SURVIVOR_QUESTIONS = 20;
|
|
|
302
302
|
/** Distinct categories a run must span for the Many-sided meta-badge. */
|
|
303
303
|
const MASTER_OF_NONE_MIN_CATEGORIES = 3;
|
|
304
304
|
/** Other badges that must be earned alongside the breadth for Many-sided. */
|
|
305
|
-
const MASTER_OF_NONE_MIN_BADGES =
|
|
305
|
+
const MASTER_OF_NONE_MIN_BADGES = 4;
|
|
306
306
|
/** Accuracy (0–1) on a hard run that earns giant-slayer. */
|
|
307
307
|
const GIANT_SLAYER_ACCURACY = 0.9;
|
|
308
308
|
/** Minimum answers before giant-slayer is meaningful. */
|
|
@@ -371,32 +371,64 @@ const LONE_TORCH_MIN_HEROES = 5;
|
|
|
371
371
|
const LONE_TORCH_MIN_SUBMITTERS = 4;
|
|
372
372
|
/** Places Ascendant must climb from the midpoint snapshot. */
|
|
373
373
|
const ASCENDANT_MIN_CLIMB = 2;
|
|
374
|
+
/** Per-round submitted/correct totals across every log (first row per round). */
|
|
375
|
+
function tallyRounds(logs) {
|
|
376
|
+
const tallies = new Map();
|
|
377
|
+
for (const log of logs.values()) {
|
|
378
|
+
const seen = new Set();
|
|
379
|
+
for (const e of log) {
|
|
380
|
+
if (seen.has(e.questionIndex))
|
|
381
|
+
continue;
|
|
382
|
+
seen.add(e.questionIndex);
|
|
383
|
+
if (!e.submitted)
|
|
384
|
+
continue;
|
|
385
|
+
let t = tallies.get(e.questionIndex);
|
|
386
|
+
if (!t) {
|
|
387
|
+
t = { submitted: 0, correct: 0 };
|
|
388
|
+
tallies.set(e.questionIndex, t);
|
|
389
|
+
}
|
|
390
|
+
t.submitted++;
|
|
391
|
+
if (e.isCorrect)
|
|
392
|
+
t.correct++;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return tallies;
|
|
396
|
+
}
|
|
397
|
+
// Keyed by the logs map so a final-scores pass tallies once for all players.
|
|
398
|
+
const tallyCache = new WeakMap();
|
|
374
399
|
/**
|
|
375
400
|
* Field-relative round counts for `playerId` from every tenured player's log.
|
|
376
401
|
* The field for a round is the other players' submitted answers; a miss-filled
|
|
377
|
-
* row (a leaver, or a timeout) is not a wrong answer and is left out.
|
|
402
|
+
* row (a leaver, or a timeout) is not a wrong answer and is left out. Round
|
|
403
|
+
* tallies are cached per `logs` map, so `logs` must not change between calls.
|
|
378
404
|
*/
|
|
379
405
|
export function fieldRoundStats(playerId, logs) {
|
|
406
|
+
let tallies = tallyCache.get(logs);
|
|
407
|
+
if (!tallies) {
|
|
408
|
+
tallies = tallyRounds(logs);
|
|
409
|
+
tallyCache.set(logs, tallies);
|
|
410
|
+
}
|
|
380
411
|
let fieldRoundsDefied = 0;
|
|
381
412
|
let soleCorrectRounds = 0;
|
|
382
|
-
|
|
413
|
+
const own = logs.get(playerId) ?? [];
|
|
414
|
+
const firstOwn = new Map();
|
|
415
|
+
for (const e of own) {
|
|
416
|
+
if (!firstOwn.has(e.questionIndex))
|
|
417
|
+
firstOwn.set(e.questionIndex, e);
|
|
418
|
+
}
|
|
419
|
+
for (const entry of own) {
|
|
383
420
|
if (!entry.isCorrect)
|
|
384
421
|
continue;
|
|
385
|
-
|
|
386
|
-
let
|
|
387
|
-
let othersCorrect = 0;
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
continue;
|
|
394
|
-
others++;
|
|
395
|
-
if (other.isCorrect)
|
|
396
|
-
othersCorrect++;
|
|
397
|
-
else
|
|
398
|
-
wrong++;
|
|
422
|
+
const t = tallies.get(entry.questionIndex);
|
|
423
|
+
let others = t?.submitted ?? 0;
|
|
424
|
+
let othersCorrect = t?.correct ?? 0;
|
|
425
|
+
const mine = firstOwn.get(entry.questionIndex);
|
|
426
|
+
if (mine?.submitted) {
|
|
427
|
+
others--;
|
|
428
|
+
if (mine.isCorrect)
|
|
429
|
+
othersCorrect--;
|
|
399
430
|
}
|
|
431
|
+
const wrong = others - othersCorrect;
|
|
400
432
|
if (others >= CASSANDRA_MIN_OTHERS &&
|
|
401
433
|
wrong / others >= CASSANDRA_WRONG_SHARE) {
|
|
402
434
|
fieldRoundsDefied++;
|
|
@@ -746,7 +778,7 @@ export function computeBadges(input) {
|
|
|
746
778
|
if (victorGates &&
|
|
747
779
|
context.rank === 1 &&
|
|
748
780
|
history.length >= 2 &&
|
|
749
|
-
history.slice(1).every((h) => h?.rank === 1)) {
|
|
781
|
+
history.slice(1).every((h) => h?.rank === 1 && h.lead)) {
|
|
750
782
|
eligible.push({ id: "undisputed", detail: "first from round two" });
|
|
751
783
|
}
|
|
752
784
|
}
|