* All 19 structures now breathe: procedural pulse-glow animation generated from
each sprite's own lit pixels (tools/animate_sprites.py), 6 frames at 130ms.
WithSpriteBody PlayRepeating()s multi-frame idles, so no trait changes needed.
* Big explosions leave scorch smudges on the ground (SmudgeLayer + LeaveSmudge
on bass_ring, both superweapon impacts, and building deaths).
* Buildings shake the screen when they die (ScreenShaker + ShakeOnDeath).
* The main menu is alive again: shellmap split into two lua-driven sides that
skirmish forever. No Bot: players, so no order-manager stall.
* Stream buffs owed by telemetry: bufferbot HP 3500->5000 (survives a second
seven-inch), drmwalker 750, scraper capacity 18.
Verified: arena smoketest PASS with all changes, shellmap lua confirmed live.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
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>