Round 4: balance-arena harness with lua telemetry, minimap radar, damage smoke, harvester-attack warning, Needle Drop + Compression maps
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>
This commit is contained in:
parent
4761245c61
commit
975f305fa1
@ -198,7 +198,7 @@ faction, or it reads as clip-art soup. Fix the Blender camera rig once, reuse.
|
||||
Learned the hard way on 2026-07-20 (phases A–F):
|
||||
|
||||
- **Smoke-test any change by launching straight into a game**: `./launch-game.sh Game.Mod=vinylgod Launch.Map=vinylgod` — `Launch.Map` accepts the map's *folder name*, no UID needed. Run ~45s, then check `~/Library/Application Support/OpenRA/Logs/` for `exception-*.log` (renamed with a timestamp on crash — absence of `exception.log` alone proves nothing).
|
||||
- **The shellmap is the integration test**: it runs a bot-vs-bot match (BotA MRP vs BotB Stream, `Bot: wax` in map.yaml) behind the main menu. If a trait wiring is broken, the menu crashes within seconds.
|
||||
- **Shellmaps FREEZE with bot players** (learned 2026-07-21): a `Bot:` player on a shellmap stalls the order manager after one world tick — the menu *looks* fine but nothing simulates. Keep the shellmap a static diorama. The real integration test is `maps/arena` (Lobby-hidden map with two `Bot: wax` players + `balance.lua` telemetry): launch with `Launch.Map=arena`, wait, then `python3 tools/balance_report.py` reads economy/army/eliminations from lua.log. A map validation error (e.g. bad `Enemies:` reference) can also silently stall a match at tick 1 — always get check-yaml to zero before trusting a run.
|
||||
- Chrome image collections must be **power-of-two** textures (a 48×48 png crashes the renderer).
|
||||
- Every rules file must use the same case for shared top-level actors: `player:` not `Player:` — mismatched case = "duplicate values" manifest error.
|
||||
- Speech notification `Sounds`/`Speech` blocks: set `DefaultVariant: .wav` (engine default is `.aud`).
|
||||
|
||||
BIN
mods/vinylgod/audio/mc_harvattack.wav
Normal file
BIN
mods/vinylgod/audio/mc_harvattack.wav
Normal file
Binary file not shown.
@ -16,6 +16,18 @@ Container@PLAYER_WIDGETS:
|
||||
Height: 23
|
||||
Align: Right
|
||||
Font: TinyBold
|
||||
Container@RADAR_BOX:
|
||||
X: WINDOW_WIDTH - 240
|
||||
Y: WINDOW_HEIGHT - 240
|
||||
Width: 232
|
||||
Height: 232
|
||||
Children:
|
||||
Radar@RADAR_MINIMAP:
|
||||
X: 4
|
||||
Y: 4
|
||||
Width: 224
|
||||
Height: 224
|
||||
WorldInteractionController: INTERACTION_CONTROLLER
|
||||
Container@SUPPORT_POWERS:
|
||||
X: 10
|
||||
Y: 45
|
||||
|
||||
40
mods/vinylgod/maps/arena/balance.lua
Normal file
40
mods/vinylgod/maps/arena/balance.lua
Normal file
@ -0,0 +1,40 @@
|
||||
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))
|
||||
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
|
||||
BIN
mods/vinylgod/maps/arena/map.bin
Normal file
BIN
mods/vinylgod/maps/arena/map.bin
Normal file
Binary file not shown.
BIN
mods/vinylgod/maps/arena/map.png
Normal file
BIN
mods/vinylgod/maps/arena/map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 B |
62
mods/vinylgod/maps/arena/map.yaml
Normal file
62
mods/vinylgod/maps/arena/map.yaml
Normal file
@ -0,0 +1,62 @@
|
||||
MapFormat: 12
|
||||
|
||||
RequiresMod: vinylgod
|
||||
|
||||
Title: Balance Arena
|
||||
|
||||
Author: Monster Robot Party
|
||||
|
||||
Tileset: VINYLGOD
|
||||
|
||||
MapSize: 34,34
|
||||
|
||||
Bounds: 1,1,32,32
|
||||
|
||||
Visibility: MissionSelector
|
||||
|
||||
Categories: Tests
|
||||
|
||||
Players:
|
||||
PlayerReference@Neutral:
|
||||
Name: Neutral
|
||||
OwnsWorld: True
|
||||
NonCombatant: True
|
||||
Faction: Random
|
||||
PlayerReference@Multi0:
|
||||
Name: Multi0
|
||||
Playable: True
|
||||
LockFaction: True
|
||||
Faction: vinylgod
|
||||
Enemies: BotA, BotB
|
||||
PlayerReference@BotA:
|
||||
Name: BotA
|
||||
Faction: vinylgod
|
||||
Bot: wax
|
||||
Enemies: BotB, Multi0
|
||||
PlayerReference@BotB:
|
||||
Name: BotB
|
||||
Faction: stream
|
||||
Bot: wax
|
||||
Enemies: BotA, Multi0
|
||||
|
||||
Actors:
|
||||
Actor0: mpspawn
|
||||
Owner: Multi0
|
||||
Location: 16,3
|
||||
Actor9: vault
|
||||
Owner: Multi0
|
||||
Location: 16,2
|
||||
Actor1: popupvan
|
||||
Owner: BotA
|
||||
Location: 7,7
|
||||
Actor2: digger
|
||||
Owner: BotA
|
||||
Location: 9,8
|
||||
Actor3: streamvan
|
||||
Owner: BotB
|
||||
Location: 26,26
|
||||
Actor4: scraper
|
||||
Owner: BotB
|
||||
Location: 24,25
|
||||
|
||||
Rules: rules.yaml
|
||||
3
mods/vinylgod/maps/arena/rules.yaml
Normal file
3
mods/vinylgod/maps/arena/rules.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
world:
|
||||
LuaScript:
|
||||
Scripts: balance.lua
|
||||
BIN
mods/vinylgod/maps/compression/map.bin
Normal file
BIN
mods/vinylgod/maps/compression/map.bin
Normal file
Binary file not shown.
BIN
mods/vinylgod/maps/compression/map.png
Normal file
BIN
mods/vinylgod/maps/compression/map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
78
mods/vinylgod/maps/compression/map.yaml
Normal file
78
mods/vinylgod/maps/compression/map.yaml
Normal file
@ -0,0 +1,78 @@
|
||||
MapFormat: 12
|
||||
|
||||
RequiresMod: vinylgod
|
||||
|
||||
Title: The Compression
|
||||
|
||||
Author: Monster Robot Party
|
||||
|
||||
Tileset: VINYLGOD
|
||||
|
||||
MapSize: 92,92
|
||||
|
||||
Bounds: 1,1,90,90
|
||||
|
||||
Visibility: Lobby
|
||||
|
||||
Categories: Maps
|
||||
|
||||
Players:
|
||||
PlayerReference@Neutral:
|
||||
Name: Neutral
|
||||
OwnsWorld: True
|
||||
NonCombatant: True
|
||||
Faction: Random
|
||||
PlayerReference@Creeps:
|
||||
Name: Creeps
|
||||
NonCombatant: True
|
||||
Faction: Random
|
||||
PlayerReference@Multi0:
|
||||
Name: Multi0
|
||||
Playable: True
|
||||
Faction: Random
|
||||
Enemies: Creeps
|
||||
PlayerReference@Multi1:
|
||||
Name: Multi1
|
||||
Playable: True
|
||||
Faction: Random
|
||||
Enemies: Creeps
|
||||
PlayerReference@Multi2:
|
||||
Name: Multi2
|
||||
Playable: True
|
||||
Faction: Random
|
||||
Enemies: Creeps
|
||||
PlayerReference@Multi3:
|
||||
Name: Multi3
|
||||
Playable: True
|
||||
Faction: Random
|
||||
Enemies: Creeps
|
||||
PlayerReference@Multi4:
|
||||
Name: Multi4
|
||||
Playable: True
|
||||
Faction: Random
|
||||
Enemies: Creeps
|
||||
PlayerReference@Multi5:
|
||||
Name: Multi5
|
||||
Playable: True
|
||||
Faction: Random
|
||||
Enemies: Creeps
|
||||
|
||||
Actors:
|
||||
Actor0: mpspawn
|
||||
Owner: Multi0
|
||||
Location: 9,9
|
||||
Actor1: mpspawn
|
||||
Owner: Multi1
|
||||
Location: 82,9
|
||||
Actor2: mpspawn
|
||||
Owner: Multi2
|
||||
Location: 9,82
|
||||
Actor3: mpspawn
|
||||
Owner: Multi3
|
||||
Location: 82,82
|
||||
Actor4: mpspawn
|
||||
Owner: Multi4
|
||||
Location: 45,7
|
||||
Actor5: mpspawn
|
||||
Owner: Multi5
|
||||
Location: 45,84
|
||||
BIN
mods/vinylgod/maps/needledrop/map.bin
Normal file
BIN
mods/vinylgod/maps/needledrop/map.bin
Normal file
Binary file not shown.
BIN
mods/vinylgod/maps/needledrop/map.png
Normal file
BIN
mods/vinylgod/maps/needledrop/map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 517 B |
46
mods/vinylgod/maps/needledrop/map.yaml
Normal file
46
mods/vinylgod/maps/needledrop/map.yaml
Normal file
@ -0,0 +1,46 @@
|
||||
MapFormat: 12
|
||||
|
||||
RequiresMod: vinylgod
|
||||
|
||||
Title: Needle Drop
|
||||
|
||||
Author: Monster Robot Party
|
||||
|
||||
Tileset: VINYLGOD
|
||||
|
||||
MapSize: 42,42
|
||||
|
||||
Bounds: 1,1,40,40
|
||||
|
||||
Visibility: Lobby
|
||||
|
||||
Categories: Maps
|
||||
|
||||
Players:
|
||||
PlayerReference@Neutral:
|
||||
Name: Neutral
|
||||
OwnsWorld: True
|
||||
NonCombatant: True
|
||||
Faction: Random
|
||||
PlayerReference@Creeps:
|
||||
Name: Creeps
|
||||
NonCombatant: True
|
||||
Faction: Random
|
||||
PlayerReference@Multi0:
|
||||
Name: Multi0
|
||||
Playable: True
|
||||
Faction: Random
|
||||
Enemies: Creeps
|
||||
PlayerReference@Multi1:
|
||||
Name: Multi1
|
||||
Playable: True
|
||||
Faction: Random
|
||||
Enemies: Creeps
|
||||
|
||||
Actors:
|
||||
Actor0: mpspawn
|
||||
Owner: Multi0
|
||||
Location: 7,20
|
||||
Actor1: mpspawn
|
||||
Owner: Multi1
|
||||
Location: 34,20
|
||||
@ -26,27 +26,29 @@ Players:
|
||||
Name: Creeps
|
||||
NonCombatant: True
|
||||
Faction: Random
|
||||
PlayerReference@BotA:
|
||||
Name: BotA
|
||||
Faction: vinylgod
|
||||
Bot: wax
|
||||
Enemies: BotB
|
||||
PlayerReference@BotB:
|
||||
Name: BotB
|
||||
Faction: stream
|
||||
Bot: wax
|
||||
Enemies: BotA
|
||||
|
||||
Actors:
|
||||
Actor1: popupvan
|
||||
Owner: BotA
|
||||
Location: 7,7
|
||||
Actor1: pressplant
|
||||
Owner: Creeps
|
||||
Location: 12,12
|
||||
Actor2: digger
|
||||
Owner: BotA
|
||||
Location: 9,8
|
||||
Actor3: streamvan
|
||||
Owner: BotB
|
||||
Location: 26,26
|
||||
Actor4: scraper
|
||||
Owner: BotB
|
||||
Location: 24,25
|
||||
Owner: Creeps
|
||||
Location: 15,14
|
||||
Actor3: ampstack
|
||||
Owner: Creeps
|
||||
Location: 10,14
|
||||
Actor4: popupshop
|
||||
Owner: Creeps
|
||||
Location: 18,12
|
||||
Actor5: hornpit
|
||||
Owner: Creeps
|
||||
Location: 16,16
|
||||
Actor6: streamhub
|
||||
Owner: Creeps
|
||||
Location: 24,24
|
||||
Actor7: transcoder
|
||||
Owner: Creeps
|
||||
Location: 21,25
|
||||
Actor8: adblocker
|
||||
Owner: Creeps
|
||||
Location: 25,22
|
||||
|
||||
@ -18,3 +18,4 @@ Speech:
|
||||
ShuffleReady: audio/mc_shuffleready
|
||||
SelectTarget: audio/mc_selecttarget
|
||||
Incoming: audio/mc_incoming
|
||||
HarvesterAttack: audio/mc_harvattack
|
||||
|
||||
@ -55,6 +55,8 @@ digger:
|
||||
|
||||
popupvan:
|
||||
Inherits: ^Unit
|
||||
MustBeDestroyed:
|
||||
RequiredForShortGame: true
|
||||
Voiced:
|
||||
VoiceSet: MrpVoice
|
||||
-WithSpriteBody:
|
||||
@ -103,6 +105,7 @@ djgrunt:
|
||||
|
||||
^EconBuilding:
|
||||
Inherits: ^EconSprite
|
||||
WithDamageOverlay:
|
||||
FireWarheadsOnDeath:
|
||||
Weapon: BuildingExplode
|
||||
Targetable:
|
||||
@ -150,6 +153,7 @@ djgrunt:
|
||||
popupshop:
|
||||
Inherits: ^EconBuilding
|
||||
Inherits@shape: ^Buildable2x2
|
||||
ProvidesRadar:
|
||||
Tooltip:
|
||||
Name: actor-popupshop.name
|
||||
Health:
|
||||
|
||||
@ -30,7 +30,10 @@ player:
|
||||
WinNotification: Win
|
||||
LoseNotification: Lose
|
||||
BaseAttackNotifier:
|
||||
HarvesterAttackNotifier:
|
||||
Notification: HarvesterAttack
|
||||
SupportPowerManager:
|
||||
ScriptTriggers:
|
||||
|
||||
editorplayer:
|
||||
Inherits: ^baseplayer
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
streamvan:
|
||||
Inherits: ^Unit
|
||||
MustBeDestroyed:
|
||||
RequiredForShortGame: true
|
||||
Voiced:
|
||||
VoiceSet: StreamVoice
|
||||
-WithSpriteBody:
|
||||
@ -29,6 +31,7 @@ streamvan:
|
||||
streamhub:
|
||||
Inherits: ^EconBuilding
|
||||
Inherits@shape: ^Buildable2x2
|
||||
ProvidesRadar:
|
||||
Tooltip:
|
||||
Name: actor-streamhub.name
|
||||
Health:
|
||||
|
||||
@ -45,6 +45,7 @@ world:
|
||||
BuildingInfluence:
|
||||
PathFinder:
|
||||
MapOptions:
|
||||
ScriptTriggers:
|
||||
Locomotor@WHEELED:
|
||||
Name: wheeled
|
||||
TerrainSpeeds:
|
||||
|
||||
BIN
mods/vinylgod/sequences/assets/smoke.png
Normal file
BIN
mods/vinylgod/sequences/assets/smoke.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 607 B |
@ -200,3 +200,16 @@ algorithm:
|
||||
Filename: sequences/assets/algorithm.png
|
||||
icon:
|
||||
Filename: sequences/assets/icon_algorithm.png
|
||||
|
||||
smoke_m:
|
||||
idle:
|
||||
Filename: sequences/assets/smoke.png
|
||||
Length: 2
|
||||
loop:
|
||||
Filename: sequences/assets/smoke.png
|
||||
Start: 2
|
||||
Length: 4
|
||||
end:
|
||||
Filename: sequences/assets/smoke.png
|
||||
Start: 6
|
||||
Length: 2
|
||||
|
||||
34
tools/balance_report.py
Normal file
34
tools/balance_report.py
Normal file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Summarize a shellmap bot-war from lua.log BAL telemetry.
|
||||
|
||||
Usage: balance_report.py [path-to-lua.log]
|
||||
"""
|
||||
import os, re, sys
|
||||
|
||||
path = sys.argv[1] if len(sys.argv) > 1 else os.path.expanduser('~/Library/Application Support/OpenRA/Logs/lua.log')
|
||||
rows = []
|
||||
for line in open(path):
|
||||
m = re.search(r'BAL t=(\d+) (\w+) cash=(\d+) stored=(\d+) units=(\d+) buildings=(\d+)', line)
|
||||
if m:
|
||||
rows.append((int(m[1]), m[2], int(m[3]), int(m[4]), int(m[5]), int(m[6])))
|
||||
m = re.search(r'BAL t=(\d+) (\w+) ELIMINATED', line)
|
||||
if m:
|
||||
print(f'*** {m[2]} ELIMINATED at tick {m[1]} (~{int(m[1]) // 25}s)')
|
||||
|
||||
if not rows:
|
||||
sys.exit('no BAL telemetry found')
|
||||
|
||||
bots = sorted({r[1] for r in rows})
|
||||
print(f'{"tick":>6} ' + ''.join(f'{b + " $/store/u/b":>28}' for b in bots))
|
||||
ticks = sorted({r[0] for r in rows})
|
||||
for t in ticks[:: max(1, len(ticks) // 12)]:
|
||||
line = f'{t:>6} '
|
||||
for b in bots:
|
||||
r = next((x for x in rows if x[0] == t and x[1] == b), None)
|
||||
line += f'{f"{r[2]}/{r[3]}/{r[4]}/{r[5]}" if r else "-":>28}'
|
||||
print(line)
|
||||
|
||||
print('\nfinal:')
|
||||
for b in bots:
|
||||
r = [x for x in rows if x[1] == b][-1]
|
||||
print(f' {b}: cash={r[2]} stored={r[3]} units={r[4]} buildings={r[5]}')
|
||||
Loading…
Reference in New Issue
Block a user