← Back to Runs
Run Detail
0ee66158-e704-49ec-8f77-9a3bb2758390
Started18/06/2026, 02:41:35
Git SHA38c4f1b0
Statedirty
Avg Error %0.37%
BH Sig Count1
Total Cases12
Passing11
Run Report—
Testcase Set Diff vs Previous Run
- Removed (166)
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_10 :: 1
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_11 :: 2
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_12 :: 3
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_13 :: 4
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_14 :: 5
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_15 :: 6
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_16 :: 7
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_17 :: 8
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_18 :: 9
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_19 :: 10
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_20 :: 11
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_21 :: 12
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_22 :: 13
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_23 :: 14
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_24 :: 15
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_25 :: 16
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_26 :: 17
- testcases/1-testcases_no-heroes_t6_single-type_nc.json :: daut_viper_9 :: 0
- testcases/2-testcases_no-heroes_t6_mixed_nc.json :: daut_viper_1 :: 0
- testcases/2-testcases_no-heroes_t6_mixed_nc.json :: daut_viper_2 :: 1
- … and 146 more
Code Changes Since Previous Run
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"}}Index: simulator/src/damage.ts===================================================================--- simulator/src/damage.ts prev run+++ simulator/src/damage.ts this run@@ -221,26 +221,25 @@} else {maxGroups.set(key, { selected: candidate, candidates: [candidate] });}} else {- applyBucketCandidateGroup(candidate, [candidate], buckets, detail, appliedEffects, rejectedEffects, consumedEffectIds);+ applyBucketCandidate(candidate, buckets, detail, appliedEffects, rejectedEffects, consumedEffectIds);}}if (!maxGroups) return;for (const group of maxGroups.values()) {applyBucketCandidateGroup(group.selected, group.candidates, buckets, detail, appliedEffects, rejectedEffects, consumedEffectIds);}}-function applyBucketCandidateGroup(+// Apply the selected candidate's value to its bucket and (in trace mode) record it; returns the+// applied percentage. Shared by the single-candidate and max-group paths.+function applySelectedBucket(selected: BucketCandidate,- candidates: BucketCandidate[],buckets: NumericDamageBuckets,detail: DamageDetail,- appliedEffects: DamageEquationTrace["appliedEffects"],- rejectedEffects: DamageEquationTrace["rejectedEffects"],- consumedEffectIds: Set<string>-): void {+ appliedEffects: DamageEquationTrace["appliedEffects"]+): number {const appliedValuePct = applyBucketValue(buckets,selected.bucket,selected.valuePct,@@ -250,21 +249,52 @@selected.bucket,selected.effect.stackingKey,selected.effect.sameEffectStacking);+ if (appliedValuePct !== 0 && detail === "full") {+ const appliedEffect: DamageEquationTrace["appliedEffects"][number] = {+ effectId: selected.effect.source.effectId ?? selected.effect.id,+ bucket: selected.bucket,+ valuePct: appliedValuePct,+ source: sourceLabel(selected.effect),+ sourceSide: selected.effect.ownerSide,+ sameEffectStacking: selected.effect.sameEffectStacking+ };+ if (selected.effect.stackingKey !== undefined) appliedEffect.stackingKey = selected.effect.stackingKey;+ appliedEffects.push(appliedEffect);+ }+ return appliedValuePct;+}++// Lone-candidate fast path (the common case): no max-stacking group, so no temporary [candidate]+// array and no suppressed-sibling bookkeeping.+function applyBucketCandidate(+ candidate: BucketCandidate,+ buckets: NumericDamageBuckets,+ detail: DamageDetail,+ appliedEffects: DamageEquationTrace["appliedEffects"],+ rejectedEffects: DamageEquationTrace["rejectedEffects"],+ consumedEffectIds: Set<string>+): void {+ const appliedValuePct = applySelectedBucket(candidate, buckets, detail, appliedEffects);if (appliedValuePct !== 0) {- if (detail === "full") {- const appliedEffect: DamageEquationTrace["appliedEffects"][number] = {- effectId: selected.effect.source.effectId ?? selected.effect.id,- bucket: selected.bucket,- valuePct: appliedValuePct,- source: sourceLabel(selected.effect),- sourceSide: selected.effect.ownerSide,- sameEffectStacking: selected.effect.sameEffectStacking- };- if (selected.effect.stackingKey !== undefined) appliedEffect.stackingKey = selected.effect.stackingKey;- appliedEffects.push(appliedEffect);- }+ if (candidate.effect.duration.type === "attack") consumedEffectIds.add(candidate.effect.id);+ } else if (detail === "full") {+ rejectedEffects.push({ effectId: candidate.effect.source.effectId ?? candidate.effect.id, reason: "same_effect_max_superseded" });+ }+}++function applyBucketCandidateGroup(+ selected: BucketCandidate,+ candidates: BucketCandidate[],+ buckets: NumericDamageBuckets,+ detail: DamageDetail,+ appliedEffects: DamageEquationTrace["appliedEffects"],+ rejectedEffects: DamageEquationTrace["rejectedEffects"],+ consumedEffectIds: Set<string>+): void {+ const appliedValuePct = applySelectedBucket(selected, buckets, detail, appliedEffects);+ if (appliedValuePct !== 0) {for (const candidate of candidates) {if (candidate.effect.duration.type === "attack") consumedEffectIds.add(candidate.effect.id);if (detail === "full" && candidate !== selected) {rejectedEffects.push({ effectId: candidate.effect.source.effectId ?? candidate.effect.id, reason: "same_effect_max_suppressed" });
Show Raw Dirty State Patch (vs Clean Baseline)
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;
Accuracy Results
12 / 12
| File | Case | # | Adj | S n | G n | S μ | G μ | Bias% | t | q | P | W |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| testcases/emulator_verified/greg_mia_combo.json | greg_mia_combo | 0 | — | 100 | 4 | 2416.2 | 2508.3 | -1.97% | -1.61 | 0.36 | P | — |
| testcases/emulator_verified/greg_mia_combo.json | greg_mia_combo | 1 | -0.05% | 100 | 5 | 3482.4 | 3432.8 | 1.06% | 5.57 | 0.0 | F | — |
| testcases/emulator_verified/greg_mia_defender_current.json | greg_mia_defender_current | 0 | — | 100 | 18 | 3347.6 | 3351.6 | -0.09% | -0.70 | 0.62 | P | — |
| testcases/emulator_verified/greg_mia_nohero_control_current.json | greg_mia_nohero_control_current | 0 | — | 1 | 3 | 3752.0 | 3752.0 | 0.00% | P | — | ||
| testcases/emulator_verified/greg_mia_nohero_control_current.json | greg_mia_nohero_control_current | 1 | +0.05% | 1 | 3 | 3652.0 | 3652.0 | 0.00% | P | — | ||
| testcases/emulator_verified/mia_only_defender_current.json | mia_only_defender_current | 0 | — | 100 | 10 | 3540.8 | 3543.9 | -0.07% | -0.63 | 0.62 | P | — |
| testcases/emulator_verified/mia_only_defender_current.json | mia_only_defender_current | 1 | — | 100 | 20 | 3439.7 | 3449.2 | -0.21% | -2.46 | 0.069 | P | — |
| testcases/emulator_verified/mia_solo.json | mia_solo | 0 | — | 100 | 1 | -269.3 | -260.0 | -0.27% | -0.97 | 0.62 | P | — |
| testcases/emulator_verified/mia_solo.json | mia_solo | 1 | — | 100 | 1 | 1297.9 | 1293.0 | 0.22% | 0.59 | 0.62 | P | — |
| testcases/emulator_verified/mia_solo.json | mia_solo | 2 | — | 100 | 1 | -185.0 | -180.0 | -0.32% | -1.01 | 0.62 | P | — |
| testcases/emulator_verified/mia_solo.json | mia_solo | 3 | — | 100 | 1 | 879.6 | 879.0 | 0.04% | 0.14 | 0.88 | P | — |
| testcases/emulator_verified/mia_solo.json | mia_solo | 4 | — | 100 | 1 | 969.9 | 967.0 | 0.21% | 0.65 | 0.62 | P | — |