← Back to Runs
Compare Runs
A (baseline)
B (current)
Avg Error A0.20%
Avg Error B0.20%
Δ Avg Error+0.00%
Improved0
Regressed1
Added0
Retired0
Testcase Delta
178 / 178
Code / Config Changes
Code Changes (Run A → Run B)
Index: simulator/config/hero_definitions/Mia.json===================================================================--- simulator/config/hero_definitions/Mia.json prev run+++ simulator/config/hero_definitions/Mia.json this run@@ -29,10 +29,9 @@"applies_to": "target"},"duration": {"type": "turn",- "value": 1,- "delay": 1+ "value": 1},"same_effect_stacking": "max"}}
Show raw per-run patches
Run A dirty state patch
diff --git a/shared/fighters_data/fighters_stats.json b/shared/fighters_data/fighters_stats.json--- a/shared/fighters_data/fighters_stats.json+++ b/shared/fighters_data/fighters_stats.json@@ -4,6 +4,11 @@"lanc": [1400.98, 1432.28, 1756.8, 1722.5],"mark": [1400.98, 1431.28, 1806.4, 1785.4]},+ "custom": {+ "inf": [1370.6, 1380.5, 1706.7, 1864.7],+ "lanc": [1370.6, 1380.5, 1820.4, 1669.3],+ "mark": [1370.6, 1380.5, 1910.9, 1732.9]+ },"daut": {"inf": [9.94,diff --git a/simulator/config/hero_definitions/Mia.json b/simulator/config/hero_definitions/Mia.json--- a/simulator/config/hero_definitions/Mia.json+++ b/simulator/config/hero_definitions/Mia.json@@ -30,7 +30,8 @@},"duration": {"type": "turn",- "value": 1+ "value": 1,+ "delay": 1},"same_effect_stacking": "max"}diff --git a/simulator/src/testcases.test.ts b/simulator/src/testcases.test.ts--- a/simulator/src/testcases.test.ts+++ b/simulator/src/testcases.test.ts@@ -237,13 +237,13 @@assert.equal(summary?.gameStatAdjustment?.unadjusted.bias_raw, -2);});-test("runTestcases skips stat rounding correction for stochastic misses", () => {+test("runTestcases applies stat rounding correction for stochastic misses", () => {const config = loadSimulatorConfig();const report = runTestcases({ matching: "greg_mia_combo", repeat: 5, calibrationReportPath: "/tmp/does-not-exist.json" }, config);const summaries = Object.values(report.testcases);assert.ok(summaries.some((summary) => summary.deterministic === false && summary.game?.passes === false));- assert.deepEqual(summaries.map((summary) => summary.gameStatAdjustment), summaries.map(() => undefined));+ assert.ok(summaries.some((summary) => summary.deterministic === false && summary.gameStatAdjustment));});test("runTestcases default round cap lets long no-hero baselines reach battle end", () => {diff --git a/simulator/src/testcases.ts b/simulator/src/testcases.ts--- a/simulator/src/testcases.ts+++ b/simulator/src/testcases.ts@@ -435,8 +435,11 @@deterministic: boolean;thresholds?: Record<string, number>;}): InternalStatAdjustment | undefined {- if (!options.deterministic || options.game.bias_raw === 0) return undefined;+ const shouldCorrect = options.deterministic ? options.game.bias_raw !== 0 : !options.game.passes;+ if (!shouldCorrect || options.game.bias_raw === 0) return undefined;const direction = -Math.sign(options.game.bias_raw);+ if (!options.deterministic) return findStochasticGameStatAdjustment(options, direction);+const maxCandidate = evaluateStatAdjustment(options, direction * STAT_ROUNDING_MAX_ADJUSTMENT);let best = maxCandidate;if (maxCandidate.mode === "deterministic_exact") return maxCandidate;@@ -465,6 +468,33 @@return best;}+function findStochasticGameStatAdjustment(options: {+ game: ParityComparisonMetrics;+ input: BattleInput;+ config: SimulatorConfig;+ job: TestcaseExecutionJob;+ reference: { n: number; mu: number; sigma: number };+ initialTroops: number;+ deterministic: boolean;+ thresholds?: Record<string, number>;+}, direction: number): InternalStatAdjustment | undefined {+ let best: InternalStatAdjustment | undefined;+ for (const value of statAdjustmentCandidates(direction)) {+ const candidate = evaluateStatAdjustment(options, value);+ if (!best || correctionScore(candidate.adjusted, options.deterministic) < correctionScore(best.adjusted, options.deterministic)) best = candidate;+ }+ if (best?.adjusted.passes) return { ...best, mode: "stochastic_tolerance" };+ return best;+}++function statAdjustmentCandidates(direction: number): number[] {+ const values: number[] = [];+ for (let step = STAT_ROUNDING_SCAN_STEPS; step >= 1; step -= 1) {+ values.push(roundStatAdjustment(direction * (STAT_ROUNDING_MAX_ADJUSTMENT * step) / STAT_ROUNDING_SCAN_STEPS));+ }+ return values;+}+function evaluateStatAdjustment(options: {game: ParityComparisonMetrics;input: BattleInput;
Run B dirty state patch
diff --git a/shared/fighters_data/fighters_stats.json b/shared/fighters_data/fighters_stats.json--- a/shared/fighters_data/fighters_stats.json+++ b/shared/fighters_data/fighters_stats.json@@ -4,6 +4,11 @@"lanc": [1400.98, 1432.28, 1756.8, 1722.5],"mark": [1400.98, 1431.28, 1806.4, 1785.4]},+ "custom": {+ "inf": [1370.6, 1380.5, 1706.7, 1864.7],+ "lanc": [1370.6, 1380.5, 1820.4, 1669.3],+ "mark": [1370.6, 1380.5, 1910.9, 1732.9]+ },"daut": {"inf": [9.94,diff --git a/simulator/src/testcases.test.ts b/simulator/src/testcases.test.ts--- a/simulator/src/testcases.test.ts+++ b/simulator/src/testcases.test.ts@@ -237,13 +237,13 @@assert.equal(summary?.gameStatAdjustment?.unadjusted.bias_raw, -2);});-test("runTestcases skips stat rounding correction for stochastic misses", () => {+test("runTestcases applies stat rounding correction for stochastic misses", () => {const config = loadSimulatorConfig();const report = runTestcases({ matching: "greg_mia_combo", repeat: 5, calibrationReportPath: "/tmp/does-not-exist.json" }, config);const summaries = Object.values(report.testcases);assert.ok(summaries.some((summary) => summary.deterministic === false && summary.game?.passes === false));- assert.deepEqual(summaries.map((summary) => summary.gameStatAdjustment), summaries.map(() => undefined));+ assert.ok(summaries.some((summary) => summary.deterministic === false && summary.gameStatAdjustment));});test("runTestcases default round cap lets long no-hero baselines reach battle end", () => {diff --git a/simulator/src/testcases.ts b/simulator/src/testcases.ts--- a/simulator/src/testcases.ts+++ b/simulator/src/testcases.ts@@ -435,8 +435,11 @@deterministic: boolean;thresholds?: Record<string, number>;}): InternalStatAdjustment | undefined {- if (!options.deterministic || options.game.bias_raw === 0) return undefined;+ const shouldCorrect = options.deterministic ? options.game.bias_raw !== 0 : !options.game.passes;+ if (!shouldCorrect || options.game.bias_raw === 0) return undefined;const direction = -Math.sign(options.game.bias_raw);+ if (!options.deterministic) return findStochasticGameStatAdjustment(options, direction);+const maxCandidate = evaluateStatAdjustment(options, direction * STAT_ROUNDING_MAX_ADJUSTMENT);let best = maxCandidate;if (maxCandidate.mode === "deterministic_exact") return maxCandidate;@@ -465,6 +468,33 @@return best;}+function findStochasticGameStatAdjustment(options: {+ game: ParityComparisonMetrics;+ input: BattleInput;+ config: SimulatorConfig;+ job: TestcaseExecutionJob;+ reference: { n: number; mu: number; sigma: number };+ initialTroops: number;+ deterministic: boolean;+ thresholds?: Record<string, number>;+}, direction: number): InternalStatAdjustment | undefined {+ let best: InternalStatAdjustment | undefined;+ for (const value of statAdjustmentCandidates(direction)) {+ const candidate = evaluateStatAdjustment(options, value);+ if (!best || correctionScore(candidate.adjusted, options.deterministic) < correctionScore(best.adjusted, options.deterministic)) best = candidate;+ }+ if (best?.adjusted.passes) return { ...best, mode: "stochastic_tolerance" };+ return best;+}++function statAdjustmentCandidates(direction: number): number[] {+ const values: number[] = [];+ for (let step = STAT_ROUNDING_SCAN_STEPS; step >= 1; step -= 1) {+ values.push(roundStatAdjustment(direction * (STAT_ROUNDING_MAX_ADJUSTMENT * step) / STAT_ROUNDING_SCAN_STEPS));+ }+ return values;+}+function evaluateStatAdjustment(options: {game: ParityComparisonMetrics;input: BattleInput;