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>
Adds tools/gen_sideb.py — one data-driven builder replacing the copy-pasted
gen_missionN.py pattern — and tools/smoketest.sh, which reports
PASS/FAIL/DISPLAY_UNAVAILABLE instead of the process-is-alive check that has
produced false passes twice now.
NOT verified in-game: the display went to sleep mid-session. Static checks all
pass (luac, check-yaml 0 errors, map refresh, check_assets, make) and the
already-verified m03 hangs identically, confirming it is environmental.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Full base-building map against a live Stream bot holding The Algorithm behind a
defended base. Building the Mastering Studio triggers a scripted all-in
counterattack; silencing The Algorithm wins. Tick scan throttled to 1Hz.
SIDE A is now three missions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Strike force + The Selector against a Stream transcoder outpost; 6-minute
countdown shown via SetMissionText, master lacquer is cut (killed) if the timer
beats you. Adds the lacquer prop (deliberately untargetable) and ScriptTriggers
on all actor templates, without which Trigger.OnKilled throws at map load.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the single flat grey tile. Adds tools/build_tiles.py (atlas builder
with edge-blending + tone matching), paint_terrain.py (surgical tile-plane
repaint: asphalt base, concrete pads, dirt blobs, groove patches near wax,
rubble clumps), jitter_wax.py, render_map.py (offline map preview), and
check_assets.py.
Also fixes the arena harness being rigged: both wax fields sat ~12 cells from
BotA and ~18 from BotB, so BotB starved for map reasons, not balance reasons.
Arena wax is now mirrored (7.1/18/18/19.8 from both starts). Seep interval
90 -> 20 after telemetry showed the slower rate still drained both economies.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds tools/check_assets.py — check-yaml does NOT resolve sprite file paths, so a
missing PNG only crashes at map load. This catches it statically.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wax seeps fix the economy flatline the arena telemetry exposed: both bots hit
cash=0/stored=0 from tick 6250 with no way to recover.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AttackMoveActivity scanned for frozen actors on a player with no
FrozenActorLayer -> NullReferenceException mid-match. Verified by a full
AI-vs-AI arena match running to completion (BotA won at tick ~10500).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes a critical bug: undeployed MCV vans now count for short-game survival —
previously every match ended at tick 1 (all players 'defeated', local player
'won'). The bot-vs-bot shellmap claim from earlier rounds was invalid for the
same reason: shellmaps stall with Bot: players and matches insta-ended. The
arena map + tools/balance_report.py is the real, verified harness.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>