1
0
Fork 0

feat(opord): add par-anchored speed bonus to scoring

This commit is contained in:
Jason Fraley 2026-09-09 11:28:39 -04:00
parent 181f244f62
commit eb77f45b95
2 changed files with 21 additions and 3 deletions

View file

@ -150,8 +150,9 @@ export function OpordArena({
<span className="font-medium text-foreground">Debrief</span>
<span>
A perfect run inside {OPORD_DIFFICULTIES[difficulty].parTimeMs / 1000}s earns a{" "}
{OPORD_DIFFICULTIES[difficulty].perfectBonus}-point bonus. Each tier tracks its own
best score.
{OPORD_DIFFICULTIES[difficulty].perfectBonus}-point bonus. Finishing under the par
time also earns a speed bonus that scales with how fast you finish and how many
answers you got right. Each tier tracks its own best score.
</span>
</div>
</CardContent>

View file

@ -26,6 +26,12 @@ export interface OpordTierConfig {
readonly parTimeMs: number;
/** Bonus for a perfect run inside the par time. */
readonly perfectBonus: number;
/**
* Points pool for the time bonus: a run finishing under the par time earns
* this scaled by the fraction of par time saved and the fraction of
* questions answered correctly, so speed pays only alongside accuracy.
*/
readonly maxSpeedBonus: number;
}
/** Grace added to the quiz limit for network/rounding before the server rejects. */
@ -50,6 +56,7 @@ export const OPORD_DIFFICULTIES: Record<OpordDifficulty, OpordTierConfig> = {
correctPoints: 100,
parTimeMs: 100_000,
perfectBonus: 150,
maxSpeedBonus: 150,
},
veteran: {
studyHintSeconds: 25,
@ -58,6 +65,7 @@ export const OPORD_DIFFICULTIES: Record<OpordDifficulty, OpordTierConfig> = {
correctPoints: 150,
parTimeMs: 150_000,
perfectBonus: 300,
maxSpeedBonus: 250,
},
elite: {
// Extra clock headroom: elite relational questions include time arithmetic.
@ -67,6 +75,7 @@ export const OPORD_DIFFICULTIES: Record<OpordDifficulty, OpordTierConfig> = {
correctPoints: 200,
parTimeMs: 200_000,
perfectBonus: 500,
maxSpeedBonus: 400,
},
};
@ -103,7 +112,15 @@ export function calculateOpordScore(
): number {
const tier = OPORD_DIFFICULTIES[difficulty];
const perfect = correct >= tier.questionCount && timeMs <= tier.parTimeMs;
return Math.max(0, correct * tier.correctPoints + (perfect ? tier.perfectBonus : 0));
const speedBonus =
timeMs < tier.parTimeMs
? Math.round(
((tier.parTimeMs - timeMs) / tier.parTimeMs) *
tier.maxSpeedBonus *
(correct / tier.questionCount),
)
: 0;
return Math.max(0, correct * tier.correctPoints + speedBonus + (perfect ? tier.perfectBonus : 0));
}
export function calculateOpordXp(score: number, isNewTierBest: boolean): number {