Two fair-map matches showed MRP annihilating Stream by ~5min. Build-order telemetry (new BALTYPES histogram in balance.lua) found the cause: Stream's podfarm arrived at 5.8min vs MRP's merchtable at 1.7min, so the cheap-swarm faction fielded 1 bufferbot against 17 djgrunts, and Stream spent 1500cr on the Algorithm superweapon before it had an army. Fixes: * BuildingFractions: barracks 1 -> 10, factories 1 -> 6 so army buildings come before tech. Result: podfarm 5.8min -> 2.5min, algorithm 3.3min -> 5.8min, and Stream survived to 9.5min with 13 buildings instead of being wiped at 5.2. * BuildingDelays on mastering/algorithm (4500) — no superweapon before an army. * UnitsToBuild shares normalised. Reading UnitBuilderBotModule showed these are PERCENTAGES of army composition, and mine summed to 233% per faction, which made unit selection collapse to iteration order and starved Stream of infantry. balance_report.py now emits a verdict (flatline / runaway / stalled production) and refuses to judge matches under 5 minutes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
1.8 KiB
Lua
61 lines
1.8 KiB
Lua
WorldLoaded = function()
|
|
print("BAL script loaded")
|
|
for _, p in ipairs(Player.GetPlayers(nil)) do
|
|
Trigger.OnPlayerWon(p, function(pl) print("BAL WON: "..pl.InternalName) end)
|
|
Trigger.OnPlayerLost(p, function(pl) print("BAL LOST: "..pl.InternalName) end)
|
|
end
|
|
end
|
|
|
|
-- Balance telemetry: logs bot economy/army every ~10s to lua.log.
|
|
-- Read with: grep BAL ~/Library/Application Support/OpenRA/Logs/lua.log
|
|
local tick = 0
|
|
local dead = {}
|
|
|
|
Tick = function()
|
|
tick = tick + 1
|
|
if tick <= 3 then print("BAL tick "..tick) end
|
|
if tick == 251 then print("BAL reached 251") end
|
|
if tick % 250 ~= 0 then
|
|
return
|
|
end
|
|
local bots = Player.GetPlayers(function(p) return p.InternalName == "BotA" or p.InternalName == "BotB" end)
|
|
for _, p in ipairs(bots) do
|
|
local units, buildings = 0, 0
|
|
for _, a in ipairs(Map.ActorsInWorld) do
|
|
if a.Owner == p then
|
|
if a.HasProperty("Move") then
|
|
units = units + 1
|
|
else
|
|
buildings = buildings + 1
|
|
end
|
|
end
|
|
end
|
|
print(string.format("BAL t=%d %s cash=%d stored=%d units=%d buildings=%d",
|
|
tick, p.InternalName, p.Cash, p.Resources, units, buildings))
|
|
|
|
-- type histogram: which buildings/units does this bot actually have?
|
|
if tick % 1250 == 0 then
|
|
local kinds = {}
|
|
local order = {}
|
|
for _, a in ipairs(Map.ActorsInWorld) do
|
|
if a.Owner == p then
|
|
if kinds[a.Type] == nil then
|
|
kinds[a.Type] = 0
|
|
order[#order + 1] = a.Type
|
|
end
|
|
kinds[a.Type] = kinds[a.Type] + 1
|
|
end
|
|
end
|
|
local parts = ""
|
|
for _, k in ipairs(order) do
|
|
parts = parts .. k .. "=" .. kinds[k] .. " "
|
|
end
|
|
print(string.format("BALTYPES t=%d %s %s", tick, p.InternalName, parts))
|
|
end
|
|
if units + buildings == 0 and not dead[p.InternalName] then
|
|
dead[p.InternalName] = true
|
|
print(string.format("BAL t=%d %s ELIMINATED", tick, p.InternalName))
|
|
end
|
|
end
|
|
end
|