claude/probability-config-review-1tdolw #119

Merged
chrisp merged 2 commits from claude/probability-config-review-1tdolw into main 2026-06-30 23:24:49 +00:00
2 changed files with 25 additions and 9 deletions
Showing only changes of commit 84b789db9b - Show all commits

View file

@ -69,6 +69,25 @@ interface ActionData {
message: string;
}
/**
* Engine knobs that a simulator (or the shared input-policy resolver) actually
* reads from config. The structured Engine fields are limited to these so the UI
* never shows a control that silently does nothing bespoke per-sim constants
* (e.g. homeFieldElo, eloDivisor, srsEloScale, raceNoise) that live in a profile
* but are not read from config stay editable only via the raw-JSON escape hatch.
*/
const HONORED_ENGINE_KNOBS = new Set([
"iterations",
"parityFactor",
"seasonGames",
"overtimeRate",
"matchParityFactor",
"averageOpponentElo",
"baseDrawRate",
"drawDecay",
"ratingScaleFactor",
]);
function parseOptionalNumber(value: string | undefined): number | null {
if (value === undefined || value.trim() === "") return null;
const parsed = Number(value);
@ -335,7 +354,7 @@ export default function AdminSportsSeasonSimulator({ loaderData }: Route.Compone
// nested object edited in its own section). Driving the fields from the merged
// config means each simulator shows exactly the knobs it actually reads.
const engineEntries = Object.entries(config.config)
.filter(([key, value]) => key !== "inputPolicy" && typeof value === "number")
.filter(([key, value]) => HONORED_ENGINE_KNOBS.has(key) && typeof value === "number")
.map(([key, value]) => [key, value as unknown as number] as [string, number]);
return (

View file

@ -254,14 +254,11 @@ export function resolveSourceElos(
);
}
if (oddsInputs.length >= 2) {
// Map onto the configured Elo band so the policy's floor/ceiling set the
// odds-derived *spread* (the real flattening dial), not just a post-hoc clamp.
const converted = convertFuturesToElo(oddsInputs, "american", {
exponent: 0.33,
eloMin: policy.eloMin,
eloMax: policy.eloMax,
});
for (const [participantId, elo] of converted) {
// Odds map onto the calibrated band (probability-engine DEFAULT_CALIBRATION);
// the policy's eloMin/eloMax then clamp the result via clampDerived below, so
// a season's Elo floor/ceiling still narrows the odds-derived spread without
// widening it (and without clamping a high directly-entered base Elo's blend).
for (const [participantId, elo] of convertFuturesToElo(oddsInputs)) {
oddsElo.set(participantId, elo);
}
}