Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
cc7667d
Add reactive Anti-Magic Shell and real Malkorok (AMS) encounter
darkmaster2133 Jul 27, 2026
10c58f9
Update DK Frost/Unholy golden test results for P5 preset changes
darkmaster2133 Jul 27, 2026
a6ecf66
Fix Malkorok encounter per review: move reactive AMS to APL, correct …
darkmaster2133 Jul 30, 2026
7dc7c69
Add dedicated Malkorok reactive-AMS presets for Frost/Unholy DK
darkmaster2133 Jul 30, 2026
78ff346
Fix native Windows dev server setup
darkmaster2133 Jul 30, 2026
bbd551a
Trim Malkorok AI comments per review, ignore throwaway BiS tools
darkmaster2133 Jul 30, 2026
3f29d2f
Update .gitignore to remove unused entries
darkmaster2133 Jul 30, 2026
c3b2770
Fix Malkorok reactive-AMS presets and encounter data per review
darkmaster2133 Jul 31, 2026
4d92ce2
Fix Malkorok reactive-AMS presets and encounter data per review
darkmaster2133 Jul 31, 2026
3f41767
Generate per-spec index.html before starting the dev server
darkmaster2133 Aug 1, 2026
5991fb3
Add boss_spell_is_known APL condition; consolidate Malkorok DK presets
darkmaster2133 Aug 1, 2026
d0fd460
Merge branch 'master' of https://github.com/darkmaster2133/mop--malko…
darkmaster2133 Aug 1, 2026
73d6cd9
Add boss_spell_known to translation schema
darkmaster2133 Aug 1, 2026
ddfc79e
Simplify Windows dev binary naming per review
darkmaster2133 Aug 2, 2026
cb153e5
Merge upstream/master into malkorok-ams
darkmaster2133 Aug 2, 2026
6859340
Fix Glyph of Regenerative Magic CD reduction not refreshing minReady …
darkmaster2133 Aug 5, 2026
08de19b
Fix AMS autocast regression from Malkorok bossSpellIsKnown check
darkmaster2133 Aug 5, 2026
b576515
Merge upstream/master into malkorok-ams
darkmaster2133 Aug 5, 2026
e4f59e5
Simplify clean target to use wowsimmop$(BIN_EXT) instead of separate …
darkmaster2133 Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified assets/database/db.bin
Binary file not shown.
3 changes: 2 additions & 1 deletion assets/database/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -18744,7 +18744,8 @@
{"path":"Throne of Thunder/Horridon 25 H P2","targets":[{"path":"Throne of Thunder/Horridon 25 H","target":{"id":68476,"name":"Horridon 25 H","level":93,"mobType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24835,0,1962616500,0,0],"minBaseDamage":512867,"damageSpread":0.5508,"swingSpeed":2,"targetInputs":[{"inputType":1,"label":"Jalak death time","tooltip":"Simulation time (in seconds) at which to disable War-God Jalak and trigger the Rampage buff on Horridon. If set longer than the simulated fight length, then Jalak will be tanked the entire time and Rampage will never be triggered.","numberValue":33},{"label":"Taunt swap for Triple Puncture","tooltip":"If checked, taunt swap upon Jalak's death and on every other Triple Puncture application afterwards in order to limit stack accumulation on a single tank.","boolValue":true}]}},{"path":"Throne of Thunder/War-God Jalak 25 H","target":{"id":69374,"name":"War-God Jalak 25 H","level":93,"mobType":6,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24835,0,78504660,0,0],"minBaseDamage":485334,"damageSpread":0.4668,"swingSpeed":2,"tankIndex":1}}]},
{"path":"Throne of Thunder/Horridon 10 H P2","targets":[{"path":"Throne of Thunder/Horridon 10 H","target":{"id":68476,"name":"Horridon 10 H","level":93,"mobType":1,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24835,0,654205500,0,0],"minBaseDamage":491480,"damageSpread":0.5508,"swingSpeed":2,"targetInputs":[{"inputType":1,"label":"Jalak death time","tooltip":"Simulation time (in seconds) at which to disable War-God Jalak and trigger the Rampage buff on Horridon. If set longer than the simulated fight length, then Jalak will be tanked the entire time and Rampage will never be triggered.","numberValue":33},{"label":"Taunt swap for Triple Puncture","tooltip":"If checked, taunt swap upon Jalak's death and on every other Triple Puncture application afterwards in order to limit stack accumulation on a single tank.","boolValue":true}]}},{"path":"Throne of Thunder/War-God Jalak 10 H","target":{"id":69374,"name":"War-God Jalak 10 H","level":93,"mobType":6,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24835,0,26168220,0,0],"minBaseDamage":423170,"damageSpread":0.4668,"swingSpeed":2,"tankIndex":1}}]},
{"path":"Siege of Orgrimmar/Iron Juggernaut 25 H - P1","targets":[{"path":"Siege of Orgrimmar/Iron Juggernaut 25 H","target":{"id":71466,"name":"Iron Juggernaut 25 H","level":93,"mobType":7,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24835,0,1500000000,0,0],"minBaseDamage":823479,"damageSpread":1.128,"swingSpeed":1.5,"secondTankIndex":1,"targetInputs":[{"inputType":1,"label":"Ignite Armor tank swap stacks","tooltip":"Taunt swap when the current tank reaches this many Ignite Armor stacks. Set to 0 to disable simulated tank swaps.","numberValue":5},{"label":"Swap back when Ignite Armor expires","tooltip":"Automatically swap back to the main tank when Ignite Armor expires on the main tank. If disabled, swap after stacks build up on the off-tank.","boolValue":true}]}}]},
{"path":"Siege of Orgrimmar/Iron Juggernaut 10 H - P1","targets":[{"path":"Siege of Orgrimmar/Iron Juggernaut 10 H","target":{"id":71466,"name":"Iron Juggernaut 10 H","level":93,"mobType":7,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24835,0,600000000,0,0],"minBaseDamage":582961,"damageSpread":0.749,"swingSpeed":1.5,"secondTankIndex":1,"targetInputs":[{"inputType":1,"label":"Ignite Armor tank swap stacks","tooltip":"Taunt swap when the current tank reaches this many Ignite Armor stacks. Set to 0 to disable simulated tank swaps.","numberValue":5},{"label":"Swap back when Ignite Armor expires","tooltip":"Automatically swap back to the main tank when Ignite Armor expires on the main tank. If disabled, swap after stacks build up on the off-tank.","boolValue":true}]}}]}
{"path":"Siege of Orgrimmar/Iron Juggernaut 10 H - P1","targets":[{"path":"Siege of Orgrimmar/Iron Juggernaut 10 H","target":{"id":71466,"name":"Iron Juggernaut 10 H","level":93,"mobType":7,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24835,0,600000000,0,0],"minBaseDamage":582961,"damageSpread":0.749,"swingSpeed":1.5,"secondTankIndex":1,"targetInputs":[{"inputType":1,"label":"Ignite Armor tank swap stacks","tooltip":"Taunt swap when the current tank reaches this many Ignite Armor stacks. Set to 0 to disable simulated tank swaps.","numberValue":5},{"label":"Swap back when Ignite Armor expires","tooltip":"Automatically swap back to the main tank when Ignite Armor expires on the main tank. If disabled, swap after stacks build up on the off-tank.","boolValue":true}]}}]},
{"path":"Siege of Orgrimmar/Malkorok (DPS) 25 H - P1","targets":[{"path":"Siege of Orgrimmar/Malkorok (DPS) 25 H","target":{"id":71454,"name":"Malkorok (DPS) 25 H","level":93,"mobType":6,"stats":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24835,0,900000000,0,0],"minBaseDamage":250000,"damageSpread":0.5,"swingSpeed":2}}]}
],
"glyphIds":[
{"itemId":40896,"spellId":54810},
Expand Down
4 changes: 4 additions & 0 deletions assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,10 @@
"label": "Is Tanking",
"tooltip": "True if the player is currently tanking"
},
"boss_spell_known": {
"label": "Boss Spell Known",
"tooltip": "<b>True</b> if the target currently has the specified spell, otherwise <b>False</b>."
},
"unit_is_moving": {
"label": "Unit Is Moving",
"tooltip": "True if the unit is moving"
Expand Down
4 changes: 4 additions & 0 deletions assets/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,10 @@
"label": "Est en train de tanker",
"tooltip": "Vrai si le joueur est actuellement en train de tanker"
},
"boss_spell_known": {
"label": "Sort de boss connu",
"tooltip": "<b>Vrai</b> si la cible possède actuellement le sort spécifié, sinon <b>Faux</b>."
},
"unit_is_moving": {
"label": "Unité en mouvement",
"tooltip": "Vrai si l'unité se déplace"
Expand Down
15 changes: 9 additions & 6 deletions makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
OUT_DIR := dist/mop
# Windows won't launch an extensionless binary -- air just pops a file-association prompt.
BIN_EXT := $(shell go env GOEXE)
TS_CORE_SRC := $(shell find ui/core -name '*.ts' -type f)
ASSETS_INPUT := $(shell find assets/ -type f)
ASSETS := $(patsubst assets/%,$(OUT_DIR)/assets/%,$(ASSETS_INPUT))
Expand Down Expand Up @@ -74,6 +76,7 @@ clean:
rm -rf ui/core/proto/*.ts \
sim/core/proto/*.pb.go \
wowsimmop \
wowsimmop.exe \
Comment thread
hillerstorm marked this conversation as resolved.
Outdated
wowsimmop-windows.exe \
wowsimmop-amd64-darwin \
wowsimmop-arm64-darwin \
Expand Down Expand Up @@ -175,7 +178,7 @@ wowsimmop: binary_dist devserver
.PHONY: devserver
devserver: sim/core/proto/api.pb.go sim/web/main.go binary_dist/dist.go
@echo "Starting server compile now..."
@if go build -o wowsimmop ./sim/web/main.go ; then \
@if go build -o wowsimmop$(BIN_EXT) ./sim/web/main.go ; then \
printf "\033[1;32mBuild Completed Successfully\033[0m\n"; \
else \
printf "\033[1;31mBUILD FAILED\033[0m\n"; \
Expand All @@ -194,9 +197,9 @@ endif
rundevserver: air devserver
ifeq ($(WATCH), 1)
npx tsx vite.build-workers.mts & npx vite build -m development --watch &
ulimit -n 10240 && air -tmp_dir "/tmp" -build.include_ext "go,proto" -build.args_bin "--usefs=true --launch=false" -build.bin "./wowsimmop" -build.cmd "make devserver" -build.exclude_dir "assets,dist,node_modules,ui,tools"
ulimit -n 10240 && air -tmp_dir "/tmp" -build.include_ext "go,proto" -build.args_bin "--usefs=true --launch=false" -build.bin "./wowsimmop$(BIN_EXT)" -build.cmd "make devserver" -build.exclude_dir "assets,dist,node_modules,ui,tools"
else
./wowsimmop --usefs=true --launch=false --host=":3333"
./wowsimmop$(BIN_EXT) --usefs=true --launch=false --host=":3333"
endif

wowsimmop-windows.exe: wowsimmop
Expand Down Expand Up @@ -301,12 +304,12 @@ else
npx http-server $(OUT_DIR)/..
endif

devmode: air devserver
devmode: air devserver $(PAGE_INDECES)
ifeq ($(WATCH), 1)
npx tsx vite.build-workers.mts & npx vite serve --host &
air -tmp_dir "/tmp" -build.include_ext "go,proto" -build.args_bin "--usefs=true --launch=false --wasm=false" -build.bin "./wowsimmop" -build.cmd "make devserver" -build.exclude_dir "assets,dist,node_modules,ui,tools"
air -tmp_dir "/tmp" -build.include_ext "go,proto" -build.args_bin "--usefs=true --launch=false --wasm=false" -build.bin "./wowsimmop$(BIN_EXT)" -build.cmd "make devserver" -build.exclude_dir "assets,dist,node_modules,ui,tools"
else
./wowsimmop --usefs=true --launch=false --host=":3333"
./wowsimmop$(BIN_EXT) --usefs=true --launch=false --host=":3333"
endif

webworkers:
Expand Down
5 changes: 5 additions & 0 deletions proto/apl.proto
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ message APLValue {
APLValueBossSpellTimeToReady boss_spell_time_to_ready = 64;
APLValueBossSpellIsCasting boss_spell_is_casting = 65;
APLValueBossCurrentTarget boss_current_target = 120;
APLValueBossSpellIsKnown boss_spell_is_known = 128;

// Resource values
APLValueCurrentHealth current_health = 26;
Expand Down Expand Up @@ -528,6 +529,10 @@ message APLValueBossSpellIsCasting {
message APLValueBossCurrentTarget {
UnitReference target_unit = 1;
}
message APLValueBossSpellIsKnown {
UnitReference target_unit = 1;
ActionID spell_id = 2;
}
message APLValueUnitIsMoving {
UnitReference source_unit = 1;
}
Expand Down
14 changes: 14 additions & 0 deletions schemas/translation.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4251,6 +4251,19 @@
"additionalProperties": false,
"required": ["label", "tooltip"]
},
"boss_spell_known": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"tooltip": {
"type": "string"
}
},
"additionalProperties": false,
"required": ["label", "tooltip"]
},
"unit_is_moving": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -5582,6 +5595,7 @@
"boss_cast",
"boss_cast_time_to_ready",
"boss_current_target",
"boss_spell_known",
"unit_is_moving",
"distance_to_unit",
"current_health",
Expand Down
2 changes: 2 additions & 0 deletions sim/core/apl_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ func (rot *APLRotation) newAPLValueWithContext(config *proto.APLValue, groupVari
value = rot.newValueBossSpellTimeToReady(config.GetBossSpellTimeToReady(), config.Uuid)
case *proto.APLValue_BossCurrentTarget:
value = rot.newValueBossCurrentTarget(config.GetBossCurrentTarget(), config.Uuid)
case *proto.APLValue_BossSpellIsKnown:
value = rot.newValueBossSpellIsKnown(config.GetBossSpellIsKnown(), config.Uuid)

// Resources
case *proto.APLValue_CurrentHealth:
Expand Down
7 changes: 7 additions & 0 deletions sim/core/apl_values_boss.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ func (value *APLValueBossSpellTimeToReady) String() string {
return fmt.Sprintf("Boss Spell Time to Ready(%s)", value.spell.ActionID)
}

func (rot *APLRotation) newValueBossSpellIsKnown(config *proto.APLValueBossSpellIsKnown, uuid *proto.UUID) APLValue {
spell := rot.GetTargetAPLSpell(config.SpellId, rot.GetTargetUnit(config.TargetUnit))
return rot.newValueConst(&proto.APLValueConst{
Val: Ternary(spell != nil, "true", "false"),
}, uuid)
}

type APLValueBossCurrentTarget struct {
DefaultAPLValueImpl
player *Unit
Expand Down
9 changes: 8 additions & 1 deletion sim/core/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func (target *Target) Enable(sim *Simulation) {
target.AutoAttacks.EnableAutoSwing(sim)

// Randomize GCD and swing timings to prevent fake APL-Haste couplings.
target.ExtendGCDUntil(sim, sim.CurrentTime+DurationFromSeconds(sim.RandomFloat("Specials Timing")*BossGCD.Seconds()))
target.RandomizeGCDTiming(sim)
target.AutoAttacks.RandomizeMeleeTiming(sim)

if !target.IsEnabled() {
Expand All @@ -247,6 +247,13 @@ func (target *Target) Enable(sim *Simulation) {
}
}

// RandomizeGCDTiming rolls this target's GCD forward by a random offset within one boss-GCD
// tick. Only called automatically from Enable(); AIs whose Reset() re-derives cast timing each
// iteration should call it again from Reset() too.
func (target *Target) RandomizeGCDTiming(sim *Simulation) {
target.ExtendGCDUntil(sim, sim.CurrentTime+DurationFromSeconds(sim.RandomFloat("Specials Timing")*BossGCD.Seconds()))
}

func (sim *Simulation) EnableTargetUnit(targetUnit *Unit) {
if targetUnit.Type != EnemyUnit {
panic("Unit is not an enemy target!")
Expand Down
25 changes: 20 additions & 5 deletions sim/death_knight/anti_magic_shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ func (dk *DeathKnight) registerAntiMagicShell() {
if hasRegenerativeMagic && antiMagicShellAura.ShieldStrength > 0 {
remainingFraction := antiMagicShellAura.ShieldStrength / currentShield
antiMagicShellSpell.CD.Reduce(time.Duration(0.5 * remainingFraction * float64(antiMagicShellSpell.CD.TimeToReady(sim))))
// CD.Reduce() changes readiness outside the normal TryActivate() path, so the
// cached minReady bailout in getFirstReadyMCD must be refreshed here -- otherwise
// autocastOtherCooldowns keeps short-circuiting past AMS until the pre-reduction
// ready time, silently eating the discount this glyph is supposed to grant.
dk.UpdateMajorCooldowns()
}
},
},
Expand Down Expand Up @@ -143,11 +148,21 @@ func (dk *DeathKnight) registerAntiMagicShell() {
},
})

// When the user models AMS damage intake, autocast the shell as a low-priority DPS
// cooldown once Runic Power is nearly empty, so the RP from the absorbed magic damage
// tops the bar back up without overcapping. Registered only when intake is configured,
// so the shell stays out of the rotation entirely when the feature is disabled. It is
// cast through the autocastOtherCooldowns action present in every DPS preset.
// Autocast the shell as a low-priority DPS cooldown, cast through the
// autocastOtherCooldowns action present in every DPS preset. Only registered when the user
// has opted into damage-intake modeling (AvgAMSHit > 0): core.AddMajorCooldown has real side
// effects beyond just enabling ShouldActivate (it unconditionally ORs SpellFlagMCD onto the
// spell, sim/core/major_cooldown.go), which measurably changed Blood's golden-output tests
// even with ShouldActivate always returning false -- so this must stay a real conditional
// registration, not "always register but never activate."
//
// Reactive casting against a specific boss's real abilities (e.g. Malkorok) isn't handled
// here -- write it directly into the APL instead, using the existing "Boss Spell Is
// Casting" / "Boss Spell Time to Ready" condition nodes against that boss's SpellFlagAPL
// spells as a manual "Cast Spell" action (see ui/death_knight/blood/apls/iron_juggernaut.apl.json
// and the equivalent Frost/Unholy default rotations for the pattern). A manual "Cast Spell"
// action's APLActionCastSpell.IsReady never looks at ShouldActivate, so a user-authored
// reactive rule like that always takes priority over this flat heuristic.
if dk.Inputs.AvgAMSHit > 0 {
Comment thread
hillerstorm marked this conversation as resolved.
dk.AddMajorCooldown(core.MajorCooldown{
Spell: antiMagicShellSpell,
Expand Down
Loading
Loading