diff --git a/MEGAPLAN.md b/MEGAPLAN.md index 9b4b74f..7058829 100644 --- a/MEGAPLAN.md +++ b/MEGAPLAN.md @@ -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`). diff --git a/mods/vinylgod/audio/mc_harvattack.wav b/mods/vinylgod/audio/mc_harvattack.wav new file mode 100644 index 0000000..d3efaa1 Binary files /dev/null and b/mods/vinylgod/audio/mc_harvattack.wav differ diff --git a/mods/vinylgod/chrome/layouts/ingame-player.yaml b/mods/vinylgod/chrome/layouts/ingame-player.yaml index e8b3dea..8ab1b06 100644 --- a/mods/vinylgod/chrome/layouts/ingame-player.yaml +++ b/mods/vinylgod/chrome/layouts/ingame-player.yaml @@ -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 diff --git a/mods/vinylgod/maps/arena/balance.lua b/mods/vinylgod/maps/arena/balance.lua new file mode 100644 index 0000000..7a446c3 --- /dev/null +++ b/mods/vinylgod/maps/arena/balance.lua @@ -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 diff --git a/mods/vinylgod/maps/arena/map.bin b/mods/vinylgod/maps/arena/map.bin new file mode 100644 index 0000000..4c21633 Binary files /dev/null and b/mods/vinylgod/maps/arena/map.bin differ diff --git a/mods/vinylgod/maps/arena/map.png b/mods/vinylgod/maps/arena/map.png new file mode 100644 index 0000000..100a5ea Binary files /dev/null and b/mods/vinylgod/maps/arena/map.png differ diff --git a/mods/vinylgod/maps/arena/map.yaml b/mods/vinylgod/maps/arena/map.yaml new file mode 100644 index 0000000..8c5c05d --- /dev/null +++ b/mods/vinylgod/maps/arena/map.yaml @@ -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 diff --git a/mods/vinylgod/maps/arena/rules.yaml b/mods/vinylgod/maps/arena/rules.yaml new file mode 100644 index 0000000..9e16162 --- /dev/null +++ b/mods/vinylgod/maps/arena/rules.yaml @@ -0,0 +1,3 @@ +world: + LuaScript: + Scripts: balance.lua diff --git a/mods/vinylgod/maps/compression/map.bin b/mods/vinylgod/maps/compression/map.bin new file mode 100644 index 0000000..bc8affc Binary files /dev/null and b/mods/vinylgod/maps/compression/map.bin differ diff --git a/mods/vinylgod/maps/compression/map.png b/mods/vinylgod/maps/compression/map.png new file mode 100644 index 0000000..2d636cd Binary files /dev/null and b/mods/vinylgod/maps/compression/map.png differ diff --git a/mods/vinylgod/maps/compression/map.yaml b/mods/vinylgod/maps/compression/map.yaml new file mode 100644 index 0000000..550793b --- /dev/null +++ b/mods/vinylgod/maps/compression/map.yaml @@ -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 diff --git a/mods/vinylgod/maps/needledrop/map.bin b/mods/vinylgod/maps/needledrop/map.bin new file mode 100644 index 0000000..dc19dfb Binary files /dev/null and b/mods/vinylgod/maps/needledrop/map.bin differ diff --git a/mods/vinylgod/maps/needledrop/map.png b/mods/vinylgod/maps/needledrop/map.png new file mode 100644 index 0000000..d404cf8 Binary files /dev/null and b/mods/vinylgod/maps/needledrop/map.png differ diff --git a/mods/vinylgod/maps/needledrop/map.yaml b/mods/vinylgod/maps/needledrop/map.yaml new file mode 100644 index 0000000..ebe7358 --- /dev/null +++ b/mods/vinylgod/maps/needledrop/map.yaml @@ -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 diff --git a/mods/vinylgod/maps/shellmap/map.yaml b/mods/vinylgod/maps/shellmap/map.yaml index d9c3520..679fe75 100644 --- a/mods/vinylgod/maps/shellmap/map.yaml +++ b/mods/vinylgod/maps/shellmap/map.yaml @@ -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 diff --git a/mods/vinylgod/notifications/vinylgod.yaml b/mods/vinylgod/notifications/vinylgod.yaml index 65fe1eb..36e50a2 100644 --- a/mods/vinylgod/notifications/vinylgod.yaml +++ b/mods/vinylgod/notifications/vinylgod.yaml @@ -18,3 +18,4 @@ Speech: ShuffleReady: audio/mc_shuffleready SelectTarget: audio/mc_selecttarget Incoming: audio/mc_incoming + HarvesterAttack: audio/mc_harvattack diff --git a/mods/vinylgod/rules/economy.yaml b/mods/vinylgod/rules/economy.yaml index 617a577..7384e42 100644 --- a/mods/vinylgod/rules/economy.yaml +++ b/mods/vinylgod/rules/economy.yaml @@ -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: diff --git a/mods/vinylgod/rules/player.yaml b/mods/vinylgod/rules/player.yaml index 3038d7b..3e8a49e 100644 --- a/mods/vinylgod/rules/player.yaml +++ b/mods/vinylgod/rules/player.yaml @@ -30,7 +30,10 @@ player: WinNotification: Win LoseNotification: Lose BaseAttackNotifier: + HarvesterAttackNotifier: + Notification: HarvesterAttack SupportPowerManager: + ScriptTriggers: editorplayer: Inherits: ^baseplayer diff --git a/mods/vinylgod/rules/stream.yaml b/mods/vinylgod/rules/stream.yaml index dfe9ff2..a2a2d70 100644 --- a/mods/vinylgod/rules/stream.yaml +++ b/mods/vinylgod/rules/stream.yaml @@ -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: diff --git a/mods/vinylgod/rules/world.yaml b/mods/vinylgod/rules/world.yaml index a506019..f95520a 100644 --- a/mods/vinylgod/rules/world.yaml +++ b/mods/vinylgod/rules/world.yaml @@ -45,6 +45,7 @@ world: BuildingInfluence: PathFinder: MapOptions: + ScriptTriggers: Locomotor@WHEELED: Name: wheeled TerrainSpeeds: diff --git a/mods/vinylgod/sequences/assets/smoke.png b/mods/vinylgod/sequences/assets/smoke.png new file mode 100644 index 0000000..ac9ae9f Binary files /dev/null and b/mods/vinylgod/sequences/assets/smoke.png differ diff --git a/mods/vinylgod/sequences/economy.yaml b/mods/vinylgod/sequences/economy.yaml index 7ef0372..75dc933 100644 --- a/mods/vinylgod/sequences/economy.yaml +++ b/mods/vinylgod/sequences/economy.yaml @@ -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 diff --git a/tools/balance_report.py b/tools/balance_report.py new file mode 100644 index 0000000..026d749 --- /dev/null +++ b/tools/balance_report.py @@ -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]}')