fix(SCOPONE-0012): complete iteration 3 - pass ai quality gate

This commit is contained in:
Giancarmine Salucci
2026-04-11 21:02:58 +02:00
parent 3d76fb544f
commit 7a64e923f1
2 changed files with 206 additions and 17 deletions

View File

@@ -1215,16 +1215,33 @@ function scoreCaptureAdv(
table,
liveSevenPressure,
);
const liveCardsMajorityRace = race.myCards < 21
&& race.oppCards < 21
&& Math.abs(race.myCards - race.oppCards) <= 5;
const protectingCardsLead = liveCardsMajorityRace && race.myCards > race.oppCards;
const cardsMajorityDelta = scoreCardsMajorityPosition(race.myCards + allCaptured.length, race.oppCards, phase)
- scoreCardsMajorityPosition(race.myCards, race.oppCards, phase);
let material = 30 + captured.length * (race.behindInCards ? 16 : 10) + phase * captured.length * 6;
material += capturedDenariCount * (race.behindInDenari ? 20 : race.denariRaceLive ? 16 : 10);
material += capturedDenariCount * (race.behindInDenari ? 20 : race.denariRaceLive ? (protectingCardsLead ? 12 : 16) : protectingCardsLead ? 7 : 10);
material += capturedSevenCount * (race.need7s ? 14 : race.sevenRaceLive ? 11 : 7);
for (const card of allCaptured) material += primieraVal(card) * 2;
material += Math.round((afterPairInventory - beforePairInventory) * 1.8);
material += directSevenPrimieraSwing;
material += Math.round(cardsMajorityDelta * (liveCardsMajorityRace ? 1.2 : 0.6));
if (protectingCardsLead && captured.length > 1) material += 96 + captured.length * 24;
if (capturesSettebello) material += 72;
if (tableHasSettebello && nextIsOpp && !capturesSettebello) material -= 84;
if (
protectingCardsLead
&& !race.behindInDenari
&& captured.length === 1
&& captured[0].suit === 'denara'
&& !capturesSettebello
) {
material -= 84;
}
if (capturedDenariCount > 0 && nextIsOpp && exposedDenariCount === 0) material += liveDenariPressure ? 30 : 14;
if (capturedSevenCount > 0 && nextIsOpp && exposedSevenCount === 0) material += liveSevenPressure ? 34 : 16;
if (
@@ -1800,6 +1817,7 @@ function scoreMoveObjectiveBias(
tracker: CardTracker | undefined,
): number {
const hand = state.players[playerIdx].hand;
const phase = gamePhase(state);
const race = getRaceState(state, playerIdx);
const roleContext = getDealerRoleContext(state, playerIdx);
const rankResidue = getRankResidueSnapshot(tracker, hand, state.table);
@@ -1842,6 +1860,14 @@ function scoreMoveObjectiveBias(
const directSevenPrimieraSwing = move.capture.length > 0
? scoreDirectSevenPrimieraSwing(move.card, move.capture, summary.projectedTable, hand, state.table, liveSevenPressure)
: 0;
const liveCardsMajorityRace = race.myCards < 21
&& race.oppCards < 21
&& Math.abs(race.myCards - race.oppCards) <= 5;
const protectingCardsLead = liveCardsMajorityRace && race.myCards > race.oppCards;
const cardsMajorityDelta = move.capture.length > 0
? scoreCardsMajorityPosition(race.myCards + capturedCards.length, race.oppCards, phase)
- scoreCardsMajorityPosition(race.myCards, race.oppCards, phase)
: 0;
const directRankCapture = move.capture.length === 1 && move.capture[0].value === move.card.value;
const directSettebelloCapture = directRankCapture
&& move.capture[0].suit === 'denara'
@@ -1865,7 +1891,18 @@ function scoreMoveObjectiveBias(
if (summary.capturesSettebello) bias += 460;
if (directRankCapture) bias += move.card.value === 7 ? 90 : 34;
if (directRankCapture && move.capture[0].value === 7) bias += liveSevenPressure ? 140 : 70;
if (directRankCapture && move.capture[0].suit === 'denara') bias += liveDenariPressure ? 150 : 72;
if (directRankCapture && move.capture[0].suit === 'denara') {
bias += liveDenariPressure ? (protectingCardsLead ? 88 : 150) : protectingCardsLead ? 36 : 72;
}
if (
protectingCardsLead
&& !race.behindInDenari
&& move.capture.length === 1
&& move.capture[0].suit === 'denara'
&& !summary.capturesSettebello
) {
bias -= 180;
}
if (directSettebelloCapture) bias += 180;
if (directSettebelloCapture && nextIsOpp) bias += 220;
if (
@@ -1884,6 +1921,10 @@ function scoreMoveObjectiveBias(
bias += openingDuplicateReleaseBias;
bias += quietControlWindow;
bias += directSevenPrimieraSwing;
bias += Math.round(cardsMajorityDelta * (liveCardsMajorityRace ? 3.6 : 1.8));
if (protectingCardsLead && move.capture.length > 1) {
bias += 220 + move.capture.length * 36;
}
bias += Math.round(handStructureDelta * 1.35);
bias += Math.round(pairInventoryDelta * (roleContext.defendingDealerAdvantage ? 6.5 : 4.5));
bias += Math.round(scoreRoleTablePlan(summary.projectedTable, roleContext, nextIsOpp) * 0.85);
@@ -1898,6 +1939,14 @@ function scoreMoveObjectiveBias(
if (move.capture.length === 0) {
if (summary.highQuietRelease) bias += 72;
bias += summary.sameValueAnchorsRemaining * 44;
if (
nextIsOpp
&& summary.projectedTable.length >= 5
&& summary.tableSum >= 24
&& (summary.exposedDenariCount > 0 || summary.exposedSevenCount > 0)
) {
bias -= 96 + summary.exposedDenariCount * 54 + summary.exposedSevenCount * 68;
}
if (exactPartnerWindow) bias += 96;
if (safePartnerWindow) bias += exactPartnerWindow ? 120 : 76;
if (
@@ -2497,16 +2546,22 @@ function scoreControlOverrideCandidate(
&& summary.highQuietRelease
&& summary.projectedTable.length >= 5
&& summary.tableSum >= 24
&& (summary.exposedDenariCount > 0 || summary.exposedSevenCount > 0)
) {
score += 260;
if (summary.exposedDenariCount === 0 && summary.exposedSevenCount === 0) {
score += 260;
} else {
score -= 80 + summary.exposedDenariCount * 90 + summary.exposedSevenCount * 120;
}
}
} else {
if (!isForcingSearchMove(summary, race)) score -= 200;
if (!isForcingSearchMove(summary, race)) {
score -= summary.projectedTable.length <= 2 || summary.tableSum <= 12 ? 200 : 80;
}
if (!summary.clearsTable && isImmediateTacticalConcession(summary.projectedTable, nextIsOpp, threats)) {
score -= 180;
}
if (nextIsOpp && summary.projectedTable.length <= 3) score -= 150;
if (nextIsOpp && summary.projectedTable.length <= 2) score -= 150;
else if (nextIsOpp && summary.projectedTable.length === 3 && summary.tableSum <= 12) score -= 90;
if (nextIsOpp) score -= summary.exposedDenariCount * 90;
if (nextIsOpp) score -= summary.exposedSevenCount * 70;
if (
@@ -3802,6 +3857,14 @@ function scoreMajorityRace(
return score;
}
function scoreCardsMajorityPosition(
myCards: number,
oppCards: number,
phase: number,
): number {
return scoreMajorityRace(myCards, oppCards, 21, Math.round(24 + phase * 22), 240);
}
function getRoundScoringCardWeight(card: Card): number {
let weight = 18 + primieraVal(card) * 3;
@@ -4043,6 +4106,7 @@ function evaluateTeamPosition(
const opp = buildTeamEvaluationSnapshot(state, opponentTeam);
const phase = gamePhase(state);
const matchWeight = mine.totalPoints >= 9 || opp.totalPoints >= 9 ? 360 : 260;
const matchPointCardsPressure = mine.totalPoints >= 9 || opp.totalPoints >= 9 ? 3.2 : 1;
let score = 0;
@@ -4050,7 +4114,7 @@ function evaluateTeamPosition(
if (mine.totalPoints >= 10 && opp.totalPoints < 10) score += 260;
if (opp.totalPoints >= 10 && mine.totalPoints < 10) score -= 260;
score += scoreMajorityRace(mine.cards, opp.cards, 21, Math.round(18 + phase * 18), 180);
score += Math.round(scoreCardsMajorityPosition(mine.cards, opp.cards, phase) * matchPointCardsPressure);
score += scoreMajorityRace(mine.denari, opp.denari, 6, Math.round(70 + phase * 22), 220);
if (mine.settebello) score += 420;
@@ -4071,6 +4135,10 @@ function evaluateTeamPosition(
score += scoreRootOpeningAnchorState(state, perspectiveTeam, rootPlayer);
score += scoreObjectiveTableExposure(state, perspectiveTeam);
score += scoreTableControlReserve(state, perspectiveTeam);
if ((mine.totalPoints >= 9 || opp.totalPoints >= 9) && state.table.length <= 2) {
score += Math.max(0, mine.cards - opp.cards) * 32;
score -= Math.max(0, opp.cards - mine.cards) * 32;
}
if (allowHiddenHands) {
score += scoreCurrentPlayerVisibleTempo(state, perspectiveTeam);
}