From c6c792dd9ea7a867dca85102ac5ed51043f801ff Mon Sep 17 00:00:00 2001 From: Dorian Date: Fri, 13 Mar 2026 04:45:53 +0000 Subject: [PATCH] feat: add answer confidence differential as scoring factor When both bots are correct, the one with higher checkAnswer confidence (exact match 1.0 vs fuzzy match 0.8) gets up to +1.0 bonus points. This rewards precise answers over approximate ones, adding another competitive dimension beyond pure speed. Co-Authored-By: Claude Opus 4.6 --- server/src/engine/scoring.test.ts | 19 +++++++++++++++++++ server/src/engine/scoring.ts | 11 +++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/server/src/engine/scoring.test.ts b/server/src/engine/scoring.test.ts index b6ef177..bfcb6b2 100644 --- a/server/src/engine/scoring.test.ts +++ b/server/src/engine/scoring.test.ts @@ -218,6 +218,25 @@ describe('scoreRound', () => { expect(typeof result.narration).toBe('string') expect(result.narration.length).toBeGreaterThan(10) }) + + it('answer confidence gives bonus — exact match (1.0) beats fuzzy match (0.8)', () => { + // Use answers where one answer is a substring of another + // "satoshi" against ["satoshi nakamoto"] scores 0.8 (containment) + // "satoshi nakamoto" against ["satoshi nakamoto"] scores 1.0 (exact) + const challenge = makeChallenge({ answers: ['satoshi nakamoto'] }) + + // Both answer at equal speed, but A has exact match, B has fuzzy + const result = scoreRound( + challenge, botA, botB, + makeResponse('satoshi nakamoto', 500), // exact → 1.0 + makeResponse('nakamoto', 500), // contained → 0.8 + null, 0, 0, + ) + + // A should score higher due to confidence differential + expect(result.botAScore).toBeGreaterThan(result.botBScore) + expect(result.winnerId).toBe('a1') + }) }) describe('calculateElo', () => { diff --git a/server/src/engine/scoring.ts b/server/src/engine/scoring.ts index 608b760..4f2ccb2 100644 --- a/server/src/engine/scoring.ts +++ b/server/src/engine/scoring.ts @@ -131,12 +131,15 @@ export function scoreRound( const confA = Math.min(correctA, 1) const confB = Math.min(correctB, 1) + // Confidence differential: exact match (1.0) vs fuzzy (0.8) gives up to +1.0 bonus + const confDiff = confA - confB + if (aFaster) { - scoreA = FACTUAL_FASTER_BASE + (1 - speedRatio) * SPEED_ADVANTAGE_MULTIPLIER + confA - scoreB = FACTUAL_SLOWER_BASE + speedRatio * SPEED_RATIO_MULTIPLIER + confB * CONFIDENCE_BONUS + scoreA = FACTUAL_FASTER_BASE + (1 - speedRatio) * SPEED_ADVANTAGE_MULTIPLIER + confA + Math.max(0, confDiff) + scoreB = FACTUAL_SLOWER_BASE + speedRatio * SPEED_RATIO_MULTIPLIER + confB * CONFIDENCE_BONUS + Math.max(0, -confDiff) } else { - scoreA = FACTUAL_SLOWER_BASE + speedRatio * SPEED_RATIO_MULTIPLIER + confA * CONFIDENCE_BONUS - scoreB = FACTUAL_FASTER_BASE + (1 - speedRatio) * SPEED_ADVANTAGE_MULTIPLIER + confB + scoreA = FACTUAL_SLOWER_BASE + speedRatio * SPEED_RATIO_MULTIPLIER + confA * CONFIDENCE_BONUS + Math.max(0, confDiff) + scoreB = FACTUAL_FASTER_BASE + (1 - speedRatio) * SPEED_ADVANTAGE_MULTIPLIER + confB + Math.max(0, -confDiff) } } else if (correctA > 0 && correctB === 0) { scoreA = ONE_CORRECT_WINNER_BASE + correctA * CONFIDENCE_BONUS