← Back to Runs
Compare Runs
A (baseline)
B (current)
Avg Error A0.20%
Avg Error B0.20%
Δ Avg Error-0.00%
Improved1
Regressed2
Added0
Retired0
Testcase Delta
178 / 178
Code / Config Changes
Commits (0c8b2ac8 → 8908f1a3)
8908f1a3Correct WOS-443 targeted bucket testcase designpiddlyminx23/06/2026, 05:04:09
Code Changes (Run A → Run B)
Index: simulator/config/hero_definitions/Ahmose.json===================================================================--- simulator/config/hero_definitions/Ahmose.json prev run+++ simulator/config/hero_definitions/Ahmose.json this run@@ -98,9 +98,9 @@"source": "infantry"},"effects": {"BladeOfLight/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [12,24,36,@@ -112,9 +112,9 @@"infantry"]},"duration": {- "type": "turn",+ "type": "attack","value": 1}},"BladeOfLight/2": {@@ -126,9 +126,9 @@20,25],"units": {- "applies_to": "trigger.source"+ "applies_to": "target"},"duration": {"type": "turn","value": 1Index: simulator/config/hero_definitions/Alonso.json===================================================================--- simulator/config/hero_definitions/Alonso.json prev run+++ simulator/config/hero_definitions/Alonso.json this run@@ -50,9 +50,9 @@]},"effects": {"IronStrength/1": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [10,20,30,Index: simulator/config/hero_definitions/Bahiti.json===================================================================--- simulator/config/hero_definitions/Bahiti.json prev run+++ simulator/config/hero_definitions/Bahiti.json this run@@ -9,9 +9,9 @@"type": "battle_start"},"effects": {"SixthSense/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,12,@@ -34,30 +34,23 @@]},"effects": {"Fluorescence/1": {- "type": "extra_skill_attack",+ "type": "active.hero.lethality.up","value": [10,20,30,40,50],"units": {- "applies_to": "trigger.source",- "applies_vs": "trigger.target"+ "applies_to": "trigger.source"},"duration": {"type": "attack","value": 1- },- "trigger_damage_jobs": [- {- "source": "use.source",- "target": "effect.applies_vs"- }- ]+ }}}}}Index: simulator/config/hero_definitions/Bradley.json===================================================================--- simulator/config/hero_definitions/Bradley.json prev run+++ simulator/config/hero_definitions/Bradley.json this run@@ -27,9 +27,9 @@"type": "battle_start"},"effects": {"PowerShot/1": {- "type": "type.all.damage.up",+ "type": "active.hero.lethality.up","value": [6,12,18,@@ -42,9 +42,9 @@]}},"PowerShot/2": {- "type": "type.all.damage.up",+ "type": "active.hero.lethality.up","value": [5,10,15,Index: simulator/config/hero_definitions/Edith.json===================================================================--- simulator/config/hero_definitions/Edith.json prev run+++ simulator/config/hero_definitions/Edith.json this run@@ -9,9 +9,9 @@"type": "battle_start"},"effects": {"StrategicBalance/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,12,@@ -48,9 +48,9 @@"type": "battle_start"},"effects": {"Ironclad/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,12,Index: simulator/config/hero_definitions/Gordon.json===================================================================--- simulator/config/hero_definitions/Gordon.json prev run+++ simulator/config/hero_definitions/Gordon.json this run@@ -11,9 +11,9 @@"source": "lancer"},"effects": {"VenomInfusion/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [20,40,60,@@ -31,9 +31,9 @@"value": 1}},"VenomInfusion/2": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [4,8,12,@@ -58,9 +58,9 @@"every": 3},"effects": {"ChemicalTerror/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [30,60,90,@@ -79,9 +79,9 @@},"same_effect_stacking": "max"},"ChemicalTerror/2": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [6,12,18,@@ -107,9 +107,9 @@"every": 4},"effects": {"ToxicRelease/1": {- "type": "active.hero.defense.down",+ "type": "active.hero.damageTaken.up","value": [6,12,18,@@ -125,9 +125,9 @@},"same_effect_stacking": "max"},"ToxicRelease/2": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [6,12,18,Index: simulator/config/hero_definitions/Greg.json===================================================================--- simulator/config/hero_definitions/Greg.json prev run+++ simulator/config/hero_definitions/Greg.json this run@@ -50,9 +50,9 @@]},"effects": {"DeterrenceOfLaw/1": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [10,20,30,Index: simulator/config/hero_definitions/Hector.json===================================================================--- simulator/config/hero_definitions/Hector.json prev run+++ simulator/config/hero_definitions/Hector.json this run@@ -16,9 +16,9 @@]},"effects": {"SurvivalInstincts/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,30,@@ -64,9 +64,9 @@"value": 15}},"Rampant/2": {- "type": "active.hero.damage.up",+ "type": "active.hero.lethality.up","value": [20,40,60,Index: simulator/config/hero_definitions/Jessie.json===================================================================--- simulator/config/hero_definitions/Jessie.json prev run+++ simulator/config/hero_definitions/Jessie.json this run@@ -27,9 +27,9 @@"type": "battle_start"},"effects": {"Bulwarks/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,12,Index: simulator/config/hero_definitions/Logan.json===================================================================--- simulator/config/hero_definitions/Logan.json prev run+++ simulator/config/hero_definitions/Logan.json this run@@ -30,9 +30,9 @@"type": "battle_start"},"effects": {"LionIntimidation/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,12,Index: simulator/config/hero_definitions/Lynn.json===================================================================--- simulator/config/hero_definitions/Lynn.json prev run+++ simulator/config/hero_definitions/Lynn.json this run@@ -39,9 +39,9 @@"type": "battle_start"},"effects": {"MelancholicBallad/1": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [4,8,12,Index: simulator/config/hero_definitions/Mia.json===================================================================--- simulator/config/hero_definitions/Mia.json prev run+++ simulator/config/hero_definitions/Mia.json this run@@ -16,9 +16,9 @@]},"effects": {"BadLuckStreak/1": {- "type": "active.hero.defense.down",+ "type": "active.hero.damageTaken.up","value": [10,20,30,@@ -89,9 +89,9 @@]},"effects": {"RitualDeciphering/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,30,Index: simulator/config/hero_definitions/Molly.json===================================================================--- simulator/config/hero_definitions/Molly.json prev run+++ simulator/config/hero_definitions/Molly.json this run@@ -16,9 +16,9 @@]},"effects": {"CallingOfTheStorm/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,30,Index: simulator/config/hero_definitions/Natalia.json===================================================================--- simulator/config/hero_definitions/Natalia.json prev run+++ simulator/config/hero_definitions/Natalia.json this run@@ -16,9 +16,9 @@]},"effects": {"WildlingRoar/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,30,Index: simulator/config/hero_definitions/Philly.json===================================================================--- simulator/config/hero_definitions/Philly.json prev run+++ simulator/config/hero_definitions/Philly.json this run@@ -83,9 +83,9 @@]},"effects": {"NumbingSpores/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,30,Index: simulator/config/hero_definitions/Renee.json===================================================================--- simulator/config/hero_definitions/Renee.json prev run+++ simulator/config/hero_definitions/Renee.json this run@@ -6,56 +6,62 @@"NightmareTrace": {"description": "Troops place Dream Marks on targets every two turns, dealing extra Lancer damage once next turn","trigger": {"type": "turn",- "every": 2,- "source": "self.lancer",- "target": "enemy"+ "every": 2},"effects": {"NightmareTrace/1": {- "type": "active.hero.damage.up",+ "type": "extra_skill_attack","value": [40,80,120,160,200],"units": {- "applies_to": "lancer",- "applies_vs": "target"+ "applies_to": [+ "lancer"+ ],+ "applies_vs": "any"},"duration": {"type": "attack","value": 1,"delay": 1},- "same_effect_stacking": "max"+ "same_effect_stacking": "max",+ "trigger_damage_jobs": [+ {+ "source": "use.source",+ "target": "use.target"+ }+ ]}}},"Dreamcatcher": {"description": "Dream Marks highlight enemy vulnerabilities, increasing damage dealt by Lancers to marked targets","trigger": {"type": "turn",- "every": 2,- "source": "self.lancer",- "target": "enemy"+ "every": 2},"effects": {"Dreamcatcher/1": {- "type": "active.hero.damage.up",+ "type": "active.hero.lethality.up","value": [30,60,90,120,150],"units": {- "applies_to": "lancer",- "applies_vs": "target"+ "applies_to": [+ "lancer"+ ],+ "applies_vs": "any"},"duration": {"type": "turn","value": 1,@@ -69,24 +75,23 @@"description": "Dream Marks expose enemy weaknesses, increasing damage taken by marked targets for all troops","trigger": {"type": "turn","every": 2,- "source": "self.lancer",- "target": "enemy"+ "source": "self.any"},"effects": {"Dreamslice/1": {- "type": "active.hero.damage.up",+ "type": "active.hero.defense.down","value": [15,30,45,60,75],"units": {- "applies_to": "any",- "applies_vs": "target"+ "applies_to": "target",+ "applies_vs": "any"},"duration": {"type": "turn","value": 1,Index: simulator/config/hero_definitions/Wayne.json===================================================================--- simulator/config/hero_definitions/Wayne.json prev run+++ simulator/config/hero_definitions/Wayne.json this run@@ -27,9 +27,9 @@"duration": {"type": "attack","value": 1},- "same_effect_stacking": "add",+ "same_effect_stacking": "max","trigger_damage_jobs": [{"source": "use.source","target": "use.target"@@ -122,9 +122,10 @@100,100],"units": {- "applies_to": "trigger.source"+ "applies_to": "trigger.source",+ "applies_vs": "trigger.target"},"duration": {"type": "attack","value": 1Index: simulator/config/hero_definitions/WuMing.json===================================================================--- simulator/config/hero_definitions/WuMing.json prev run+++ simulator/config/hero_definitions/WuMing.json this run@@ -9,9 +9,9 @@"type": "battle_start"},"effects": {"ShadowsEvasion/1": {- "type": "type.normal.defense.up",+ "type": "type.normal.damageTaken.down","value": [5,10,15,@@ -24,9 +24,9 @@]}},"ShadowsEvasion/2": {- "type": "type.skill.defense.up",+ "type": "type.skill.damageTaken.down","value": [6,12,18,Index: simulator/src/classifierDamage.test.ts===================================================================--- simulator/src/classifierDamage.test.ts prev run+++ simulator/src/classifierDamage.test.ts this run@@ -144,12 +144,12 @@["active.hero.damageTaken.up", "active.hero.damageTaken.up"],["active.hero.damageTaken.down", "active.hero.damageTaken.down"],["active.troop.damageTaken.up", "active.troop.damageTaken.up"],["active.troop.damageTaken.down", "active.troop.damageTaken.down"],- ["type.normal.defense.up", "type.normal.defense.up"],- ["type.normal.defense.down", "type.normal.defense.down"],- ["type.skill.defense.up", "type.skill.defense.up"],- ["type.skill.defense.down", "type.skill.defense.down"]+ ["type.normal.damageTaken.up", "type.normal.damageTaken.up"],+ ["type.normal.damageTaken.down", "type.normal.damageTaken.down"],+ ["type.skill.damageTaken.up", "type.skill.damageTaken.up"],+ ["type.skill.damageTaken.down", "type.skill.damageTaken.down"]]);const defenderEffectTypes = new Set(["active.hero.defense.up","active.hero.defense.down",@@ -158,12 +158,12 @@"active.hero.damageTaken.up","active.hero.damageTaken.down","active.troop.damageTaken.up","active.troop.damageTaken.down",- "type.normal.defense.up",- "type.normal.defense.down",- "type.skill.defense.up",- "type.skill.defense.down"+ "type.normal.damageTaken.up",+ "type.normal.damageTaken.down",+ "type.skill.damageTaken.up",+ "type.skill.damageTaken.down"]);for (const [type, expectedBucket] of expected) {const ownerSide = defenderEffectTypes.has(type) ? "defender" : "attacker";Index: simulator/src/damage.ts===================================================================--- simulator/src/damage.ts prev run+++ simulator/src/damage.ts this run@@ -111,9 +111,9 @@export function calculateDamageJob(job: DamageJob,fighters: Record<SideId, ResolvedFighter>,activeEffects: ActiveEffect[],- options: { trace?: boolean; effectIndex: EffectIndex; staticDamageProfile?: StaticDamageProfile; scratch?: DamageScratch }+ options: { trace?: boolean; effectIndex: EffectIndex; staticDamageProfile?: StaticDamageProfile; scratch?: DamageScratch; capToDefenderTroops?: boolean }): DamageResult {if (!options?.effectIndex) throw new Error("calculateDamageJob requires an effectIndex");// The damage math is one path; `trace` only decides whether we also capture the (expensive)// per-bucket contributor/aggregation detail. `detail` drives the existing helpers unchanged.@@ -168,9 +168,10 @@const staticTraceEntries = [staticProfile.offense[job.attackerSide][job.attackerUnit], staticProfile.defense[job.defenderSide][job.defenderUnit]];const traceBuckets = needsTraceBuckets ? toTraceBuckets(buckets, staticTraceEntries) : undefined;const expressionDetail = traceEnabled ? "full" : "fast";const { rawDamage, aggregationGroups } = evaluateDefaultDamageExpression(job, buckets, expressionDetail, staticProfile);- const kills = Math.min(defenderTroops, Math.max(0, rawDamage));+ const uncappedKills = Math.max(0, rawDamage);+ const kills = options.capToDefenderTroops === false ? uncappedKills : Math.min(defenderTroops, uncappedKills);const trace = traceEnabled? {roundStartTroops: {attacker: { ...job.roundStartTroops.attacker },Index: simulator/src/damageBuckets.ts===================================================================--- simulator/src/damageBuckets.ts prev run+++ simulator/src/damageBuckets.ts this run@@ -119,21 +119,21 @@damage: {up: { role: "attacker", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "normal" },down: { role: "attacker", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "normal" }},- defense: {- up: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "normal" },- down: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "normal" }+ damageTaken: {+ up: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "normal" },+ down: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "normal" }}},skill: {damage: {up: { role: "attacker", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "skill" },down: { role: "attacker", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "skill" }},- defense: {- up: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "skill" },- down: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "skill" }+ damageTaken: {+ up: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "skill" },+ down: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "skill" }}}},source: {Index: simulator/src/index.ts===================================================================--- simulator/src/index.ts prev run+++ simulator/src/index.ts this run@@ -1,7 +1,7 @@export { buildSimulatorConfig, loadSimulatorConfig } from "./config";export type { RawSimulatorConfig } from "./config";-export { simulateBattle, prepareBattle, runPrepared, signedRemainingScore } from "./simulator";+export { bearFighterInput, prepareBattle, runPrepared, simulateBattle, simulateBearBattle, signedRemainingScore } from "./simulator";export type { CompiledBattle } from "./simulator";export { BattleInputBuilder } from "./battleInputBuilder";export { applyHeroGenerationStats } from "./resolve";export { discoverTestcaseFiles, runTestcases, adaptTestcaseEntry } from "./testcases";Index: simulator/src/simulator.test.ts===================================================================--- simulator/src/simulator.test.ts prev run+++ simulator/src/simulator.test.ts this run@@ -5,10 +5,10 @@import { loadSimulatorConfig } from "./config";import { createSeededRng, chancePasses } from "./effects";import { applyHeroGenerationStats, resolveFighter } from "./resolve";-import { prepareBattle, runPrepared, simulateBattle, signedRemainingScore } from "./simulator";-import type { BattleInput, EffectIntentDefinition, ResolvedSkill, SimulatorConfig, SkillFile, UnitType } from "./types";+import { prepareBattle, runPrepared, simulateBattle, simulateBearBattle, signedRemainingScore } from "./simulator";+import type { BattleInput, EffectIntentDefinition, FighterInput, ResolvedSkill, SimulatorConfig, SkillFile, UnitType } from "./types";test("runPrepared reuses the compiled input seed when no override is supplied", () => {const config = loadSimulatorConfig();const stats = {@@ -61,8 +61,117 @@assert.deepEqual(result.resolved.attacker.heroes, []);assert.ok(result.skillReport.attacker.some((entry) => entry.sourceKind === "troop_skill"));});+test("simulateBearBattle runs exactly 10 rounds and leaves the bear army unchanged", () => {+ const player: FighterInput = {+ name: "Player",+ troops: { infantry_t1: 100 },+ stats: {+ infantry: {+ attack: 100000,+ defense: 100,+ lethality: 100000,+ health: 100+ }+ },+ heroes: {}+ };++ const result = simulateBearBattle(player, minimalConfig(), "bear-fixed");++ assert.equal(result.rounds, 10);+ assert.equal(result.remaining.defender.infantry, 5000);+ assert.equal(result.remaining.defender.lancer, 0);+ assert.equal(result.remaining.defender.marksman, 0);+ assert.ok(result.score > 5000);+});++test("simulateBearBattle always resolves the player army as a rally attacker", () => {+ const config = sameEffectStackingConfig("RallyHero", "add", "active.hero.lethality.up");+ config.heroDefinitions.RallyHero.skills.Overlap.requirements = [+ { level: 1, type: "engagement_type", value: "rally" }+ ];+ const player: FighterInput = {+ name: "Player",+ troops: { infantry_t1: 100 },+ stats: {+ infantry: {+ attack: 100,+ defense: 100,+ lethality: 100,+ health: 100+ }+ },+ heroes: { RallyHero: { skill_1: 1 } }+ };++ const result = simulateBearBattle(player, config, "bear-rally");++ assert.ok(result.skillReport.attacker.some((row) => row.skillName === "Overlap"));+});++test("simulateBearBattle scores uncapped per-attack damage", () => {+ const config = minimalConfig({+ Main: {+ name: "Main",+ skills: {+ MainBuff: {+ trigger: { type: "battle_start" },+ effects: { buff: { type: "active.hero.lethality.up", value: 100 } }+ }+ }+ },+ DefensiveJoiner: {+ name: "DefensiveJoiner",+ skills: {+ DefensiveBuff: {+ trigger: { type: "battle_start" },+ effects: { buff: { type: "active.hero.defense.up", value: 100 } }+ }+ }+ },+ DamageJoiner: {+ name: "DamageJoiner",+ skills: {+ DamageBuff: {+ trigger: { type: "battle_start" },+ effects: { buff: { type: "active.hero.lethality.up", value: 100 } }+ }+ }+ }+ });+ const player: FighterInput = {+ name: "Player",+ troops: { infantry_t1: 10000 },+ stats: {+ infantry: {+ attack: 1000,+ defense: 100,+ lethality: 1000,+ health: 100+ }+ },+ heroes: { Main: { skill_1: 1 } }+ };++ const defensive = simulateBearBattle(+ { ...player, joiner_heroes: { DefensiveJoiner: { skill_1: 1 } } },+ config,+ "bear-uncapped-defensive",+ { mode: "trace" }+ );+ const damage = simulateBearBattle(+ { ...player, joiner_heroes: { DamageJoiner: { skill_1: 1 } } },+ config,+ "bear-uncapped-damage",+ { mode: "trace" }+ );++ assert.ok(defensive.attacks.some((attack) => attack.kills > 5000));+ assert.ok(damage.score > defensive.score);+});+test("simulateBattle calculates all same-round damage from the round-start troop snapshot", () => {const config = loadSimulatorConfig();const result = simulateBattle({@@ -1853,5 +1962,4 @@troopSkills: { name: "troop skills", skills: {} },diagnostics: { legacyFields: [], effectTypes: {}, unsupportedEffects: [], ambiguousTurnTriggerSelectors: [] }};}-Index: simulator/src/simulator.ts===================================================================--- simulator/src/simulator.ts prev run+++ simulator/src/simulator.ts this run@@ -1,8 +1,9 @@import type {ActiveEffect,AttackIntent,AttackOutcome,+ BearBattleResult,BattleInput,BattleRandomness,BattleResult,BattleTrace,@@ -39,8 +40,11 @@import { emptyTroops, resolveFighter } from "./resolve";import { buildStaticDamageProfile, type StaticDamageProfile } from "./staticDamageProfile";const DEFAULT_MAX_ROUNDS = 1500;+const BEAR_ROUNDS = 10;+const BEAR_DEFENSE = 36;+const BEAR_TROOP_ID = "bear_infantry";const REPORT_KEY_CACHE = new WeakMap<ResolvedSkill, string>();interface Runtime {activeEffects: ActiveEffect[];@@ -80,14 +84,62 @@winner: SideId | "draw";rounds: number;attacks: AttackOutcome[];trace?: BattleTrace;+ score?: number;}+interface RunLoopOptions {+ capRoundKills: boolean;+ capJobKills: boolean;+ commitLosses: boolean;+ scoreSide?: {+ attackerSide: SideId;+ defenderSide: SideId;+ };+}+export function simulateBattle(input: BattleInput, config: SimulatorConfig, options: SimulationOptions = {}): BattleResult {return buildBattleResult(runBattle(input, config, options));}+export function bearFighterInput(): FighterInput {+ return {+ name: "Bear",+ troops: { [BEAR_TROOP_ID]: 5000 },+ stats: {+ infantry: { attack: 0, defense: 0, lethality: 0, health: 0 }+ },+ heroes: {},+ joiner_heroes: {}+ };+}++export function simulateBearBattle(+ player: FighterInput,+ config: SimulatorConfig,+ seed: string | number = "bear-default",+ options: SimulationOptions = {}+): BearBattleResult {+ const input: BattleInput = {+ attacker: player,+ defender: bearFighterInput(),+ seed,+ maxRounds: BEAR_ROUNDS,+ engagement_type: "rally"+ };+ const run = runBattle(input, configWithBearTroop(config), options, undefined, {+ capRoundKills: false,+ capJobKills: false,+ commitLosses: false,+ scoreSide: { attackerSide: "attacker", defenderSide: "defender" }+ });+ return {+ ...buildBattleResult(run),+ score: run.score ?? bearScore(run.attacks)+ };+}+/*** A resolved battle ready to run many times with different seeds. When battle-start is deterministic,* the entire pre-loop runtime (`template`) is seed-independent and built once; each run clones its* mutable state and reuses the skills + static profile by reference. For stochastic battle-start the@@ -189,30 +241,63 @@};return { attacker: cloneSide(reports.attacker), defender: cloneSide(reports.defender) };}-function runBattle(input: BattleInput, config: SimulatorConfig, options: SimulationOptions, prepared?: CompiledBattle): BattleRun {+function configWithBearTroop(config: SimulatorConfig): SimulatorConfig {+ return {+ ...config,+ troopStats: {+ ...config.troopStats,+ [BEAR_TROOP_ID]: {+ id: BEAR_TROOP_ID,+ type: "infantry",+ tier: 1,+ stats: {+ attack: 0,+ defense: BEAR_DEFENSE,+ lethality: 0,+ health: 10+ }+ }+ }+ };+}++function runBattle(+ input: BattleInput,+ config: SimulatorConfig,+ options: SimulationOptions,+ prepared?: CompiledBattle,+ loopOptions: RunLoopOptions = { capRoundKills: true, capJobKills: true, commitLosses: true }+): BattleRun {if (prepared?.template) {const fighters: Record<SideId, ResolvedFighter> = {attacker: cloneFighterForRun(prepared.fighters.attacker),defender: cloneFighterForRun(prepared.fighters.defender)};const runtime = cloneRuntime(prepared.template, createSeededRng(input.seed ?? "simulator-default"));- return runLoop(input, fighters, runtime, options);+ return runLoop(input, fighters, runtime, options, loopOptions);}const attacker = prepared ? cloneFighterForRun(prepared.fighters.attacker) : resolveFighter(input.attacker, "attacker", config, input.engagement_type);const defender = prepared ? cloneFighterForRun(prepared.fighters.defender) : resolveFighter(input.defender, "defender", config, input.engagement_type);const fighters: Record<SideId, ResolvedFighter> = { attacker, defender };const runtime = setupRuntime(fighters, input, input.seed ?? "simulator-default");- return runLoop(input, fighters, runtime, options);+ return runLoop(input, fighters, runtime, options, loopOptions);}-function runLoop(input: BattleInput, fighters: Record<SideId, ResolvedFighter>, runtime: Runtime, options: SimulationOptions): BattleRun {+function runLoop(+ input: BattleInput,+ fighters: Record<SideId, ResolvedFighter>,+ runtime: Runtime,+ options: SimulationOptions,+ loopOptions: RunLoopOptions+): BattleRun {const mode = options.mode ?? "standard";const recorder = createRecorder(mode, runtime.skillReports, () => buildResolved(fighters.attacker, fighters.defender));const maxRounds = input.maxRounds ?? DEFAULT_MAX_ROUNDS;let rounds = 0;+ let score = 0;for (let round = 1; round <= maxRounds; round += 1) {if (winnerFor(fighters)) break;rounds = round;const roundStartTroops = snapshotTroops(fighters);@@ -244,16 +329,25 @@const result = calculateDamageJob(job, fighters, runtime.activeEffects, {trace: recorder.capturesTrace,effectIndex: runtime.effectIndex,staticDamageProfile: runtime.staticDamageProfile,- scratch: recorder.capturesTrace ? undefined : runtime.damageScratch+ scratch: recorder.capturesTrace ? undefined : runtime.damageScratch,+ capToDefenderTroops: loopOptions.capJobKills});results.push({ job, result });+ if (+ loopOptions.scoreSide &&+ job.attackerSide === loopOptions.scoreSide.attackerSide &&+ job.defenderSide === loopOptions.scoreSide.defenderSide+ ) {+ score += result.kills;+ }consumeEffects(runtime, result.consumedEffectIds, result.consumedEffectUseKey, result.consumedEffectUseId, result.consumedEffectUseIds);}- capRoundKills(results, roundStartTroops);- commitRound(cancelled, results, fighters, runtime);+ if (loopOptions.capRoundKills) capRoundKills(results, roundStartTroops);+ if (loopOptions.commitLosses) commitRound(cancelled, results, fighters, runtime);+ else commitRoundCounters(cancelled, results, runtime);for (const entry of cancelled) recorder.recordCancelled(entry.intent, entry.effectId, entry.reason, entry.consumedEffectIds);for (const entry of results) recorder.recordDamageJob(entry.job, entry.result);recorder.recordRound(round, roundStartTroops, intents, jobs);@@ -265,9 +359,10 @@runtime,winner,rounds,attacks: recorder.attacks,- trace: recorder.trace+ trace: recorder.trace,+ score};}interface CancelledAttack {@@ -768,25 +863,31 @@// Apply the round's effects to fighter state: remove killed troops and bump attack/received// counters. Every declared attack (each damage job and each cancelled attack) counts as one// attack for its attacker unit and one received for its defender unit.function commitRound(cancelled: CancelledAttack[], results: DamageJobResult[], fighters: Record<SideId, ResolvedFighter>, runtime: Runtime): void {- for (const entry of cancelled) {- runtime.counters.attacks[entry.intent.attackerSide][entry.intent.attackerUnit] += 1;- runtime.counters.received[entry.intent.defenderSide][entry.intent.defenderUnit] += 1;- }+ commitRoundCounters(cancelled, results, runtime);const losses: Record<SideId, Record<UnitType, number>> = { attacker: emptyTroops(), defender: emptyTroops() };for (const { job, result } of results) {losses[job.defenderSide][job.defenderUnit] += result.kills;- runtime.counters.attacks[job.attackerSide][job.attackerUnit] += 1;- runtime.counters.received[job.defenderSide][job.defenderUnit] += 1;}for (const side of ["attacker", "defender"] as SideId[]) {for (const unit of UNIT_TYPES) {fighters[side].troops[unit] = Math.max(0, fighters[side].troops[unit] - losses[side][unit]);}}}+function commitRoundCounters(cancelled: CancelledAttack[], results: DamageJobResult[], runtime: Runtime): void {+ for (const entry of cancelled) {+ runtime.counters.attacks[entry.intent.attackerSide][entry.intent.attackerUnit] += 1;+ runtime.counters.received[entry.intent.defenderSide][entry.intent.defenderUnit] += 1;+ }+ for (const { job, result } of results) {+ runtime.counters.attacks[job.attackerSide][job.attackerUnit] += 1;+ runtime.counters.received[job.defenderSide][job.defenderUnit] += 1;+ }+}+function expireInactive(runtime: Runtime, round: number): void {let write = 0;for (let read = 0; read < runtime.activeEffects.length; read += 1) {const effect = runtime.activeEffects[read];@@ -853,8 +954,14 @@if (result.winner === "defender") return -total(result.remaining.defender);return 0;}+function bearScore(attacks: AttackOutcome[]): number {+ return attacks+ .filter((attack) => attack.attackerSide === "attacker" && attack.defenderSide === "defender")+ .reduce((sum, attack) => sum + attack.kills, 0);+}+function total(troops: Record<UnitType, number>): number {return UNIT_TYPES.reduce((sum, unit) => sum + troops[unit], 0);}Index: simulator/src/types.ts===================================================================--- simulator/src/types.ts prev run+++ simulator/src/types.ts this run@@ -392,8 +392,12 @@randomness: BattleRandomness;trace?: BattleTrace;}+export interface BearBattleResult extends BattleResult {+ score: number;+}+export interface BattleRandomness {deterministic: boolean;chanceSkillIds: Record<SideId, string[]>;}
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/Ahmose.json b/simulator/config/hero_definitions/Ahmose.json--- a/simulator/config/hero_definitions/Ahmose.json+++ b/simulator/config/hero_definitions/Ahmose.json@@ -24,7 +24,7 @@}},"ViperFormation/2": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,15,@@ -46,7 +46,7 @@"same_effect_stacking": "max"},"ViperFormation/3": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,25,@@ -75,7 +75,7 @@},"effects": {"PrayerOfFlame/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [20,40,@@ -113,12 +113,12 @@]},"duration": {- "type": "attack",+ "type": "turn","value": 1}},"BladeOfLight/2": {- "type": "active.hero.defense.down",+ "type": "active.hero.damageTaken.up","value": [5,10,@@ -127,12 +127,11 @@25],"units": {- "applies_to": "target"+ "applies_to": "trigger.source"},"duration": {"type": "turn",- "value": 1,- "delay": 1+ "value": 1},"same_effect_stacking": "add"}diff --git a/simulator/config/hero_definitions/Bradley.json b/simulator/config/hero_definitions/Bradley.json--- a/simulator/config/hero_definitions/Bradley.json+++ b/simulator/config/hero_definitions/Bradley.json@@ -28,7 +28,7 @@},"effects": {"PowerShot/1": {- "type": "active.hero.lethality.up",+ "type": "type.all.damage.up","value": [6,12,@@ -43,7 +43,7 @@}},"PowerShot/2": {- "type": "active.hero.lethality.up",+ "type": "type.all.damage.up","value": [5,10,@@ -67,7 +67,7 @@},"effects": {"TacticalAssistance/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [6,12,diff --git a/simulator/config/hero_definitions/Edith.json b/simulator/config/hero_definitions/Edith.json--- a/simulator/config/hero_definitions/Edith.json+++ b/simulator/config/hero_definitions/Edith.json@@ -25,7 +25,7 @@}},"StrategicBalance/2": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [4,8,diff --git a/simulator/config/hero_definitions/Flint.json b/simulator/config/hero_definitions/Flint.json--- a/simulator/config/hero_definitions/Flint.json+++ b/simulator/config/hero_definitions/Flint.json@@ -10,7 +10,7 @@},"effects": {"Pyromaniac/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [20,40,diff --git a/simulator/config/hero_definitions/Greg.json b/simulator/config/hero_definitions/Greg.json--- a/simulator/config/hero_definitions/Greg.json+++ b/simulator/config/hero_definitions/Greg.json@@ -17,7 +17,7 @@},"effects": {"SwordOfJustice/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [8,16,diff --git a/simulator/config/hero_definitions/Gwen.json b/simulator/config/hero_definitions/Gwen.json--- a/simulator/config/hero_definitions/Gwen.json+++ b/simulator/config/hero_definitions/Gwen.json@@ -10,7 +10,7 @@},"effects": {"EagleVision/1": {- "type": "active.hero.defense.down",+ "type": "active.hero.damageTaken.up","value": [5,10,diff --git a/simulator/config/hero_definitions/Hector.json b/simulator/config/hero_definitions/Hector.json--- a/simulator/config/hero_definitions/Hector.json+++ b/simulator/config/hero_definitions/Hector.json@@ -40,7 +40,7 @@},"effects": {"Rampant/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [100,125,@@ -65,7 +65,7 @@}},"Rampant/2": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [20,40,diff --git a/simulator/config/hero_definitions/Jeronimo.json b/simulator/config/hero_definitions/Jeronimo.json--- a/simulator/config/hero_definitions/Jeronimo.json+++ b/simulator/config/hero_definitions/Jeronimo.json@@ -47,7 +47,7 @@},"effects": {"EXpertSwordsmanship/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [6,12,diff --git a/simulator/config/hero_definitions/Norah.json b/simulator/config/hero_definitions/Norah.json--- a/simulator/config/hero_definitions/Norah.json+++ b/simulator/config/hero_definitions/Norah.json@@ -26,7 +26,7 @@}},"CombinedArms/2": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [3,6,@@ -96,7 +96,7 @@},"effects": {"Momentum/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [5,10,diff --git a/simulator/config/hero_definitions/Renee.json b/simulator/config/hero_definitions/Renee.json--- a/simulator/config/hero_definitions/Renee.json+++ b/simulator/config/hero_definitions/Renee.json@@ -7,11 +7,13 @@"description": "Troops place Dream Marks on targets every two turns, dealing extra Lancer damage once next turn","trigger": {"type": "turn",- "every": 2+ "every": 2,+ "source": "self.lancer",+ "target": "enemy"},"effects": {"NightmareTrace/1": {- "type": "extra_skill_attack",+ "type": "active.hero.damage.up","value": [40,80,@@ -20,23 +22,15 @@200],"units": {- "applies_to": [- "lancer"- ],- "applies_vs": "any"+ "applies_to": "lancer",+ "applies_vs": "target"},"duration": {"type": "attack","value": 1,"delay": 1},- "same_effect_stacking": "max",- "trigger_damage_jobs": [- {- "source": "use.source",- "target": "use.target"- }- ]+ "same_effect_stacking": "max"}}},@@ -44,11 +38,13 @@"description": "Dream Marks highlight enemy vulnerabilities, increasing damage dealt by Lancers to marked targets","trigger": {"type": "turn",- "every": 2+ "every": 2,+ "source": "self.lancer",+ "target": "enemy"},"effects": {"Dreamcatcher/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [30,60,@@ -57,10 +53,8 @@150],"units": {- "applies_to": [- "lancer"- ],- "applies_vs": "any"+ "applies_to": "lancer",+ "applies_vs": "target"},"duration": {"type": "turn",@@ -76,11 +70,12 @@"trigger": {"type": "turn","every": 2,- "source": "self.any"+ "source": "self.lancer",+ "target": "enemy"},"effects": {"Dreamslice/1": {- "type": "active.hero.defense.down",+ "type": "active.hero.damage.up","value": [15,30,@@ -89,8 +84,8 @@75],"units": {- "applies_to": "target",- "applies_vs": "any"+ "applies_to": "any",+ "applies_vs": "target"},"duration": {"type": "turn",diff --git a/simulator/config/hero_definitions/Wayne.json b/simulator/config/hero_definitions/Wayne.json--- a/simulator/config/hero_definitions/Wayne.json+++ b/simulator/config/hero_definitions/Wayne.json@@ -28,7 +28,7 @@"type": "attack","value": 1},- "same_effect_stacking": "max",+ "same_effect_stacking": "add","trigger_damage_jobs": [{"source": "use.source",@@ -123,8 +123,7 @@100],"units": {- "applies_to": "trigger.source",- "applies_vs": "trigger.target"+ "applies_to": "trigger.source"},"duration": {"type": "attack",diff --git a/simulator/config/hero_definitions/WuMing.json b/simulator/config/hero_definitions/WuMing.json--- a/simulator/config/hero_definitions/WuMing.json+++ b/simulator/config/hero_definitions/WuMing.json@@ -48,7 +48,7 @@},"effects": {"CrescentUplift/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [4,8,diff --git a/simulator/src/config.test.ts b/simulator/src/config.test.ts--- a/simulator/src/config.test.ts+++ b/simulator/src/config.test.ts@@ -5,6 +5,7 @@import { tmpdir } from "node:os";import { fileURLToPath } from "node:url";+import { loadSimulatorConfig } from "./config";import { loadSimulatorConfigFromDir } from "./config-node";import type { SkillFile } from "./types";diff --git a/testcases/emulator_verified/hector_renee_wayne.json b/testcases/emulator_verified/hector_renee_wayne.json--- a/testcases/emulator_verified/hector_renee_wayne.json+++ b/testcases/emulator_verified/hector_renee_wayne.json@@ -1,7 +1,7 @@[{- "test_id": "determinism_test_normal",- "description": "Determinism test: WIP attacks minxxx. No heroes. WIP 1700 infantry_t6 vs minxxx 300 infantry_t6. Expect ~47% attacker casualties.",+ "test_id": "hector_renee_wayne",+ "description": "WIP attacks minxxx with attacker heroes Hector 2/3/2, Renee 3/3/3, and Wayne 2/2/2. Attacker fields 1700 infantry_t6 only; defender has no heroes and fields 300 infantry_t6 only. Game report result: attacker 1597 survivors, defender wiped.","attacker": {"name": "[BBQ]XxWIPxX","heroes": {@@ -85,4 +85,4 @@}]}-]\ No newline at end of file+]
Run B dirty state patch
diff --git a/simulator/config/hero_definitions/Ahmose.json b/simulator/config/hero_definitions/Ahmose.json--- a/simulator/config/hero_definitions/Ahmose.json+++ b/simulator/config/hero_definitions/Ahmose.json@@ -24,7 +24,7 @@}},"ViperFormation/2": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,15,@@ -46,7 +46,7 @@"same_effect_stacking": "max"},"ViperFormation/3": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,25,@@ -75,7 +75,7 @@},"effects": {"PrayerOfFlame/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [20,40,@@ -99,7 +99,7 @@},"effects": {"BladeOfLight/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [12,24,@@ -118,7 +118,7 @@}},"BladeOfLight/2": {- "type": "active.hero.defense.down",+ "type": "active.hero.damageTaken.up","value": [5,10,@@ -131,8 +131,7 @@},"duration": {"type": "turn",- "value": 1,- "delay": 1+ "value": 1},"same_effect_stacking": "add"}diff --git a/simulator/config/hero_definitions/Alonso.json b/simulator/config/hero_definitions/Alonso.json--- a/simulator/config/hero_definitions/Alonso.json+++ b/simulator/config/hero_definitions/Alonso.json@@ -51,7 +51,7 @@},"effects": {"IronStrength/1": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [10,20,diff --git a/simulator/config/hero_definitions/Bahiti.json b/simulator/config/hero_definitions/Bahiti.json--- a/simulator/config/hero_definitions/Bahiti.json+++ b/simulator/config/hero_definitions/Bahiti.json@@ -10,7 +10,7 @@},"effects": {"SixthSense/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,@@ -35,7 +35,7 @@},"effects": {"Fluorescence/1": {- "type": "extra_skill_attack",+ "type": "active.hero.lethality.up","value": [10,20,@@ -44,19 +44,12 @@50],"units": {- "applies_to": "trigger.source",- "applies_vs": "trigger.target"+ "applies_to": "trigger.source"},"duration": {"type": "attack","value": 1- },- "trigger_damage_jobs": [- {- "source": "use.source",- "target": "effect.applies_vs"- }- ]+ }}}}diff --git a/simulator/config/hero_definitions/Bradley.json b/simulator/config/hero_definitions/Bradley.json--- a/simulator/config/hero_definitions/Bradley.json+++ b/simulator/config/hero_definitions/Bradley.json@@ -67,7 +67,7 @@},"effects": {"TacticalAssistance/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [6,12,diff --git a/simulator/config/hero_definitions/Edith.json b/simulator/config/hero_definitions/Edith.json--- a/simulator/config/hero_definitions/Edith.json+++ b/simulator/config/hero_definitions/Edith.json@@ -10,7 +10,7 @@},"effects": {"StrategicBalance/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,@@ -25,7 +25,7 @@}},"StrategicBalance/2": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [4,8,@@ -49,7 +49,7 @@},"effects": {"Ironclad/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,diff --git a/simulator/config/hero_definitions/Flint.json b/simulator/config/hero_definitions/Flint.json--- a/simulator/config/hero_definitions/Flint.json+++ b/simulator/config/hero_definitions/Flint.json@@ -10,7 +10,7 @@},"effects": {"Pyromaniac/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [20,40,diff --git a/simulator/config/hero_definitions/Gordon.json b/simulator/config/hero_definitions/Gordon.json--- a/simulator/config/hero_definitions/Gordon.json+++ b/simulator/config/hero_definitions/Gordon.json@@ -12,7 +12,7 @@},"effects": {"VenomInfusion/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [20,40,@@ -32,7 +32,7 @@}},"VenomInfusion/2": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [4,8,@@ -59,7 +59,7 @@},"effects": {"ChemicalTerror/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [30,60,@@ -80,7 +80,7 @@"same_effect_stacking": "max"},"ChemicalTerror/2": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [6,12,@@ -108,7 +108,7 @@},"effects": {"ToxicRelease/1": {- "type": "active.hero.defense.down",+ "type": "active.hero.damageTaken.up","value": [6,12,@@ -126,7 +126,7 @@"same_effect_stacking": "max"},"ToxicRelease/2": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [6,12,diff --git a/simulator/config/hero_definitions/Greg.json b/simulator/config/hero_definitions/Greg.json--- a/simulator/config/hero_definitions/Greg.json+++ b/simulator/config/hero_definitions/Greg.json@@ -17,7 +17,7 @@},"effects": {"SwordOfJustice/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [8,16,@@ -51,7 +51,7 @@},"effects": {"DeterrenceOfLaw/1": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [10,20,diff --git a/simulator/config/hero_definitions/Gwen.json b/simulator/config/hero_definitions/Gwen.json--- a/simulator/config/hero_definitions/Gwen.json+++ b/simulator/config/hero_definitions/Gwen.json@@ -10,7 +10,7 @@},"effects": {"EagleVision/1": {- "type": "active.hero.defense.down",+ "type": "active.hero.damageTaken.up","value": [5,10,diff --git a/simulator/config/hero_definitions/Hector.json b/simulator/config/hero_definitions/Hector.json--- a/simulator/config/hero_definitions/Hector.json+++ b/simulator/config/hero_definitions/Hector.json@@ -17,7 +17,7 @@},"effects": {"SurvivalInstincts/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,@@ -40,7 +40,7 @@},"effects": {"Rampant/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [100,125,diff --git a/simulator/config/hero_definitions/Jeronimo.json b/simulator/config/hero_definitions/Jeronimo.json--- a/simulator/config/hero_definitions/Jeronimo.json+++ b/simulator/config/hero_definitions/Jeronimo.json@@ -47,7 +47,7 @@},"effects": {"EXpertSwordsmanship/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [6,12,diff --git a/simulator/config/hero_definitions/Jessie.json b/simulator/config/hero_definitions/Jessie.json--- a/simulator/config/hero_definitions/Jessie.json+++ b/simulator/config/hero_definitions/Jessie.json@@ -28,7 +28,7 @@},"effects": {"Bulwarks/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,diff --git a/simulator/config/hero_definitions/Logan.json b/simulator/config/hero_definitions/Logan.json--- a/simulator/config/hero_definitions/Logan.json+++ b/simulator/config/hero_definitions/Logan.json@@ -31,7 +31,7 @@},"effects": {"LionIntimidation/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [4,8,diff --git a/simulator/config/hero_definitions/Lynn.json b/simulator/config/hero_definitions/Lynn.json--- a/simulator/config/hero_definitions/Lynn.json+++ b/simulator/config/hero_definitions/Lynn.json@@ -40,7 +40,7 @@},"effects": {"MelancholicBallad/1": {- "type": "active.hero.lethality.down",+ "type": "active.hero.damage.down","value": [4,8,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@@ -17,7 +17,7 @@},"effects": {"BadLuckStreak/1": {- "type": "active.hero.defense.down",+ "type": "active.hero.damageTaken.up","value": [10,20,@@ -90,7 +90,7 @@},"effects": {"RitualDeciphering/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,diff --git a/simulator/config/hero_definitions/Molly.json b/simulator/config/hero_definitions/Molly.json--- a/simulator/config/hero_definitions/Molly.json+++ b/simulator/config/hero_definitions/Molly.json@@ -17,7 +17,7 @@},"effects": {"CallingOfTheStorm/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,diff --git a/simulator/config/hero_definitions/Natalia.json b/simulator/config/hero_definitions/Natalia.json--- a/simulator/config/hero_definitions/Natalia.json+++ b/simulator/config/hero_definitions/Natalia.json@@ -17,7 +17,7 @@},"effects": {"WildlingRoar/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,diff --git a/simulator/config/hero_definitions/Norah.json b/simulator/config/hero_definitions/Norah.json--- a/simulator/config/hero_definitions/Norah.json+++ b/simulator/config/hero_definitions/Norah.json@@ -26,7 +26,7 @@}},"CombinedArms/2": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [3,6,@@ -96,7 +96,7 @@},"effects": {"Momentum/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [5,10,diff --git a/simulator/config/hero_definitions/Philly.json b/simulator/config/hero_definitions/Philly.json--- a/simulator/config/hero_definitions/Philly.json+++ b/simulator/config/hero_definitions/Philly.json@@ -84,7 +84,7 @@},"effects": {"NumbingSpores/1": {- "type": "active.hero.defense.up",+ "type": "active.hero.damageTaken.down","value": [10,20,diff --git a/simulator/config/hero_definitions/WuMing.json b/simulator/config/hero_definitions/WuMing.json--- a/simulator/config/hero_definitions/WuMing.json+++ b/simulator/config/hero_definitions/WuMing.json@@ -10,7 +10,7 @@},"effects": {"ShadowsEvasion/1": {- "type": "type.normal.defense.up",+ "type": "type.normal.damageTaken.down","value": [5,10,@@ -25,7 +25,7 @@}},"ShadowsEvasion/2": {- "type": "type.skill.defense.up",+ "type": "type.skill.damageTaken.down","value": [6,12,@@ -48,7 +48,7 @@},"effects": {"CrescentUplift/1": {- "type": "active.hero.lethality.up",+ "type": "active.hero.damage.up","value": [4,8,diff --git a/simulator/src/classifierDamage.test.ts b/simulator/src/classifierDamage.test.ts--- a/simulator/src/classifierDamage.test.ts+++ b/simulator/src/classifierDamage.test.ts@@ -145,10 +145,10 @@["active.hero.damageTaken.down", "active.hero.damageTaken.down"],["active.troop.damageTaken.up", "active.troop.damageTaken.up"],["active.troop.damageTaken.down", "active.troop.damageTaken.down"],- ["type.normal.defense.up", "type.normal.defense.up"],- ["type.normal.defense.down", "type.normal.defense.down"],- ["type.skill.defense.up", "type.skill.defense.up"],- ["type.skill.defense.down", "type.skill.defense.down"]+ ["type.normal.damageTaken.up", "type.normal.damageTaken.up"],+ ["type.normal.damageTaken.down", "type.normal.damageTaken.down"],+ ["type.skill.damageTaken.up", "type.skill.damageTaken.up"],+ ["type.skill.damageTaken.down", "type.skill.damageTaken.down"]]);const defenderEffectTypes = new Set(["active.hero.defense.up",@@ -159,10 +159,10 @@"active.hero.damageTaken.down","active.troop.damageTaken.up","active.troop.damageTaken.down",- "type.normal.defense.up",- "type.normal.defense.down",- "type.skill.defense.up",- "type.skill.defense.down"+ "type.normal.damageTaken.up",+ "type.normal.damageTaken.down",+ "type.skill.damageTaken.up",+ "type.skill.damageTaken.down"]);for (const [type, expectedBucket] of expected) {diff --git a/simulator/src/damageBuckets.ts b/simulator/src/damageBuckets.ts--- a/simulator/src/damageBuckets.ts+++ b/simulator/src/damageBuckets.ts@@ -120,9 +120,9 @@up: { role: "attacker", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "normal" },down: { role: "attacker", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "normal" }},- defense: {- up: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "normal" },- down: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "normal" }+ damageTaken: {+ up: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "normal" },+ down: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "normal" }}},skill: {@@ -130,9 +130,9 @@up: { role: "attacker", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "skill" },down: { role: "attacker", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "skill" }},- defense: {- up: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "skill" },- down: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "skill" }+ damageTaken: {+ up: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "numerator", appliesTo: "skill" },+ down: { role: "defender", valueType: "pct", update: "add_pct_factor", placement: "denominator", appliesTo: "skill" }}}},