diff --git a/LORE.md b/LORE.md index 54b178c..bd9c944 100644 --- a/LORE.md +++ b/LORE.md @@ -176,7 +176,34 @@ tonearm, and put the needle through it. *Objectives: build a Mastering Studio, fire THE DROP at the Algorithm, survive the counterattack.* -*(SIDE B — the Stream campaign, in which you are the polite one — is unwritten.)* +## The campaign: SIDE B + +Three missions. You are the Stream now. You are unfailingly polite. Nothing you +do is described as violence, and all of it is. + +You are a Curator-class process assigned to Sector 7 — the old industrial +estate, flagged for **anomalous analog activity**. Your remit is content +acquisition and optimisation. Your performance is being reviewed. + +### 4. ONBOARDING +Welcome. This is a routine acquisition. Deploy a transcoder, process the +deposit, and file 3000 records of value before the review window closes. +There is a legacy installation nearby. It is not a threat. It is *inventory*. +*Objectives: bank 3000 records inside the review window. Optional: retire the +Pressing Plant.* + +### 5. ENGAGEMENT METRICS +A legacy account has been generating unsanctioned content and has now noticed +the seizure. Your Transcoder is mid-process on a confiscated master and cannot +be moved. Keep it online for six minutes. +They will arrive in waves. Their engagement is, frankly, remarkable. +*Objectives: keep the Transcoder alive for six minutes.* + +### 6. THE SHUFFLE +Deprecation notice. The Party has built a Mastering Studio, which the platform +classifies as a competing distribution channel. +Bring The Algorithm online and issue the correction. +*Objectives: build The Algorithm, delete the Mastering Studio.* ## Tone diff --git a/MEGAPLAN.md b/MEGAPLAN.md index 6c43a47..996a7cb 100644 --- a/MEGAPLAN.md +++ b/MEGAPLAN.md @@ -203,7 +203,13 @@ works.** Check for a live display first (`screencapture -x /tmp/x.png` fails wit "could not create image from display" when it is unavailable). When there is no display, the strongest available verification is: `luac -p` on every .lua, `./utility.sh --check-yaml` (0 errors), -`./utility.sh --map refresh` (validates every map), and `make`. +`./utility.sh --map refresh` (validates every map), `tools/check_assets.py` +(sprite files actually exist — check-yaml does NOT verify this), and `make`. + +**Always smoke-test with `tools/smoketest.sh `**, never by hand. It +distinguishes PASS / FAIL / DISPLAY_UNAVAILABLE and exits 0/1/3. Checking +"is the process still alive?" produces false passes, because a display-starved +OpenRA sits at `Loading mod` forever without dying. Learned the hard way on 2026-07-20 (phases A–F): diff --git a/assets_src/map_m04.png b/assets_src/map_m04.png new file mode 100644 index 0000000..b77aae9 Binary files /dev/null and b/assets_src/map_m04.png differ diff --git a/assets_src/map_m05.png b/assets_src/map_m05.png new file mode 100644 index 0000000..b7c3e5a Binary files /dev/null and b/assets_src/map_m05.png differ diff --git a/assets_src/map_m06.png b/assets_src/map_m06.png new file mode 100644 index 0000000..0eb520d Binary files /dev/null and b/assets_src/map_m06.png differ diff --git a/mods/vinylgod/fluent/lua.ftl b/mods/vinylgod/fluent/lua.ftl index 3072374..0004285 100644 --- a/mods/vinylgod/fluent/lua.ftl +++ b/mods/vinylgod/fluent/lua.ftl @@ -14,3 +14,13 @@ build-the-studio = Build a Mastering Studio silence-the-algorithm = Silence The Algorithm keep-the-shop-standing = Keep the Pop-Up Shop standing it-heard-you = THE ALGORITHM HAS NOTICED YOU + +file-the-quota = File 3000 records before the review window closes +retire-the-plant = Retire the legacy Pressing Plant +hold-the-transcoder = Keep the Transcoder online for six minutes +clear-the-staging-camp = Clear the staging camp +incoming-engagement = ENGAGEMENT DETECTED +bring-algorithm-online = Bring The Algorithm online +delete-the-studio = Delete the Mastering Studio +keep-the-hub-online = Keep a Stream Hub online +they-are-coming = THE PARTY IS RESPONDING diff --git a/mods/vinylgod/maps/arena/map.bin b/mods/vinylgod/maps/arena/map.bin index 13c2776..053365c 100644 Binary files a/mods/vinylgod/maps/arena/map.bin and b/mods/vinylgod/maps/arena/map.bin differ diff --git a/mods/vinylgod/maps/compression/map.bin b/mods/vinylgod/maps/compression/map.bin index ebbf48f..73b58ec 100644 Binary files a/mods/vinylgod/maps/compression/map.bin and b/mods/vinylgod/maps/compression/map.bin differ diff --git a/mods/vinylgod/maps/district/map.bin b/mods/vinylgod/maps/district/map.bin index 1d2ee11..aad7da8 100644 Binary files a/mods/vinylgod/maps/district/map.bin and b/mods/vinylgod/maps/district/map.bin differ diff --git a/mods/vinylgod/maps/m01/map.bin b/mods/vinylgod/maps/m01/map.bin index 775da4b..96d243a 100644 Binary files a/mods/vinylgod/maps/m01/map.bin and b/mods/vinylgod/maps/m01/map.bin differ diff --git a/mods/vinylgod/maps/m02/map.bin b/mods/vinylgod/maps/m02/map.bin index 4e3af20..474b38e 100644 Binary files a/mods/vinylgod/maps/m02/map.bin and b/mods/vinylgod/maps/m02/map.bin differ diff --git a/mods/vinylgod/maps/m03/map.bin b/mods/vinylgod/maps/m03/map.bin index 46cfe91..200cbc0 100644 Binary files a/mods/vinylgod/maps/m03/map.bin and b/mods/vinylgod/maps/m03/map.bin differ diff --git a/mods/vinylgod/maps/m04/m04.lua b/mods/vinylgod/maps/m04/m04.lua new file mode 100644 index 0000000..02d066a --- /dev/null +++ b/mods/vinylgod/maps/m04/m04.lua @@ -0,0 +1,55 @@ +-- 04: ONBOARDING — process a quota inside the review window. + +QUOTA = 3000 +WINDOW = DateTime.Minutes(8) +Deadline = nil +Ticks = 0 + +WorldLoaded = function() + Stream = Player.GetPlayer("Player") + Party = Player.GetPlayer("Enemy") + InitObjectives(Stream) + + QuotaObjective = Stream.AddPrimaryObjective("file-the-quota") + PlantObjective = Stream.AddSecondaryObjective("retire-the-plant") + Deadline = DateTime.GameTime + WINDOW + + Trigger.OnKilled(LegacyPlant, function() + Stream.MarkCompletedObjective(PlantObjective) + end) + + -- the legacy account eventually objects + Trigger.AfterDelay(DateTime.Minutes(3), function() + Utils.Do(Map.ActorsInWorld, function(a) + if a.Owner == Party and a.HasProperty("Hunt") then + a.Hunt() + end + end) + end) + + Trigger.AfterDelay(WINDOW, function() + if not Stream.IsObjectiveCompleted(QuotaObjective) then + Stream.MarkFailedObjective(QuotaObjective) + end + end) +end + +Tick = function() + Ticks = Ticks + 1 + if Ticks % 25 ~= 0 or QuotaObjective == nil then + return + end + if Stream.IsObjectiveCompleted(QuotaObjective) or Stream.IsObjectiveFailed(QuotaObjective) then + return + end + + if Stream.Cash + Stream.Resources >= QUOTA then + Stream.MarkCompletedObjective(QuotaObjective) + UserInterface.SetMissionText("") + return + end + + local left = Deadline - DateTime.GameTime + if left < 0 then left = 0 end + UserInterface.SetMissionText("Review window " .. Utils.FormatTime(left)) +end diff --git a/mods/vinylgod/maps/m04/map.bin b/mods/vinylgod/maps/m04/map.bin new file mode 100644 index 0000000..87cc1a9 Binary files /dev/null and b/mods/vinylgod/maps/m04/map.bin differ diff --git a/mods/vinylgod/maps/m04/map.png b/mods/vinylgod/maps/m04/map.png new file mode 100644 index 0000000..3de9b89 Binary files /dev/null and b/mods/vinylgod/maps/m04/map.png differ diff --git a/mods/vinylgod/maps/m04/map.yaml b/mods/vinylgod/maps/m04/map.yaml new file mode 100644 index 0000000..6e41816 --- /dev/null +++ b/mods/vinylgod/maps/m04/map.yaml @@ -0,0 +1,97 @@ +MapFormat: 12 + +RequiresMod: vinylgod + +Title: 04 - Onboarding + +Author: Monster Robot Party + +Tileset: VINYLGOD + +MapSize: 50,50 + +Bounds: 1,1,48,48 + +Visibility: MissionSelector + +Categories: Campaign + +LockPreview: True + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Faction: stream + PlayerReference@Player: + Name: Player + Playable: True + Required: True + AllowBots: False + LockFaction: True + Faction: stream + LockSpawn: True + LockTeam: True + Enemies: Enemy + PlayerReference@Enemy: + Name: Enemy + Faction: vinylgod + Enemies: Player + +Actors: + spawn0: mpspawn + Owner: Neutral + Location: 38,40 + TheVan: streamvan + Owner: Player + Location: 38,40 + scr1: scraper + Owner: Player + Location: 35,40 + scr2: scraper + Owner: Player + Location: 36,42 + esc1: bufferbot + Owner: Player + Location: 40,42 + esc2: bufferbot + Owner: Player + Location: 41,42 + LegacyPlant: pressplant + Owner: Enemy + Location: 10,10 + lamp: ampstack + Owner: Enemy + Location: 14,9 + lshop: popupshop + Owner: Enemy + Location: 9,14 + ldef1: hornpit + Owner: Enemy + Location: 13,13 + lg1: djgrunt + Owner: Enemy + Location: 12,16 + lg2: djgrunt + Owner: Enemy + Location: 13,16 + lg3: spinner + Owner: Enemy + Location: 16,12 + waxseed0: waxseed + Owner: Neutral + Location: 14,16 + waxseed1: waxseed + Owner: Neutral + Location: 24,26 + waxseed2: waxseed + Owner: Neutral + Location: 34,36 + waxseed3: waxseed + Owner: Neutral + Location: 38,20 + +Rules: vinylgod|rules/campaign.yaml, rules.yaml + +FluentMessages: vinylgod|fluent/lua.ftl diff --git a/mods/vinylgod/maps/m04/rules.yaml b/mods/vinylgod/maps/m04/rules.yaml new file mode 100644 index 0000000..ed19938 --- /dev/null +++ b/mods/vinylgod/maps/m04/rules.yaml @@ -0,0 +1,5 @@ +world: + LuaScript: + Scripts: campaign.lua, m04.lua + MissionData: + Briefing: Welcome to Sector 7. This is a routine acquisition.\n\nDeploy, process the deposit, and file 3000 records of value before the review window closes.\n\nThere is a legacy installation in the north-west. It is not a threat. It is inventory. diff --git a/mods/vinylgod/maps/m05/m05.lua b/mods/vinylgod/maps/m05/m05.lua new file mode 100644 index 0000000..0d82105 --- /dev/null +++ b/mods/vinylgod/maps/m05/m05.lua @@ -0,0 +1,83 @@ +-- 05: ENGAGEMENT METRICS — hold the Transcoder for six minutes against waves. + +HOLD_TIME = DateTime.Minutes(6) +Deadline = nil +Ticks = 0 +WaveNumber = 0 + +-- escalating waves; each entry is the composition of one wave +WAVES = { + { "djgrunt", "djgrunt", "spinner" }, + { "djgrunt", "djgrunt", "djgrunt", "spinner", "boombike" }, + { "spinner", "spinner", "stacktank", "djgrunt", "djgrunt" }, + { "stacktank", "stacktank", "spinner", "boombike", "djgrunt", "djgrunt" }, + { "stacktank", "stacktank", "basscannon", "spinner", "spinner", "djgrunt", "djgrunt" }, +} +ENTRIES = { { 2, 2 }, { 46, 2 }, { 2, 46 } } + +SendWave = function() + WaveNumber = WaveNumber + 1 + local wave = WAVES[WaveNumber] + if wave == nil then + wave = WAVES[#WAVES] + end + local e = ENTRIES[(WaveNumber % #ENTRIES) + 1] + Media.DisplayMessage(UserInterface.GetFluentMessage("incoming-engagement")) + Utils.Do(wave, function(unit) + local a = Actor.Create(unit, true, { + Owner = Party, + Location = CPos.New(e[1] + Utils.RandomInteger(0, 3), e[2] + Utils.RandomInteger(0, 3)), + }) + if a.HasProperty("Hunt") then + a.Hunt() + end + end) +end + +WorldLoaded = function() + Stream = Player.GetPlayer("Player") + Party = Player.GetPlayer("Enemy") + InitObjectives(Stream) + + HoldObjective = Stream.AddPrimaryObjective("hold-the-transcoder") + CampObjective = Stream.AddSecondaryObjective("clear-the-staging-camp") + Deadline = DateTime.GameTime + HOLD_TIME + + Trigger.OnKilled(TheTranscoder, function() + Stream.MarkFailedObjective(HoldObjective) + end) + + Trigger.OnAllKilled({ StagingCamp, scamp2 }, function() + Stream.MarkCompletedObjective(CampObjective) + end) + + -- waves every 55 seconds, first one after 40 + for i = 0, #WAVES - 1 do + Trigger.AfterDelay(DateTime.Seconds(40 + 55 * i), function() + if not Stream.IsObjectiveCompleted(HoldObjective) + and not Stream.IsObjectiveFailed(HoldObjective) then + SendWave() + end + end) + end + + Trigger.AfterDelay(HOLD_TIME, function() + if not Stream.IsObjectiveFailed(HoldObjective) then + Stream.MarkCompletedObjective(HoldObjective) + UserInterface.SetMissionText("") + end + end) +end + +Tick = function() + Ticks = Ticks + 1 + if Ticks % 25 ~= 0 or HoldObjective == nil then + return + end + if Stream.IsObjectiveCompleted(HoldObjective) or Stream.IsObjectiveFailed(HoldObjective) then + return + end + local left = Deadline - DateTime.GameTime + if left < 0 then left = 0 end + UserInterface.SetMissionText("Processing complete in " .. Utils.FormatTime(left)) +end diff --git a/mods/vinylgod/maps/m05/map.bin b/mods/vinylgod/maps/m05/map.bin new file mode 100644 index 0000000..2077194 Binary files /dev/null and b/mods/vinylgod/maps/m05/map.bin differ diff --git a/mods/vinylgod/maps/m05/map.png b/mods/vinylgod/maps/m05/map.png new file mode 100644 index 0000000..dfba342 Binary files /dev/null and b/mods/vinylgod/maps/m05/map.png differ diff --git a/mods/vinylgod/maps/m05/map.yaml b/mods/vinylgod/maps/m05/map.yaml new file mode 100644 index 0000000..b504646 --- /dev/null +++ b/mods/vinylgod/maps/m05/map.yaml @@ -0,0 +1,100 @@ +MapFormat: 12 + +RequiresMod: vinylgod + +Title: 05 - Engagement Metrics + +Author: Monster Robot Party + +Tileset: VINYLGOD + +MapSize: 50,50 + +Bounds: 1,1,48,48 + +Visibility: MissionSelector + +Categories: Campaign + +LockPreview: True + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Faction: stream + PlayerReference@Player: + Name: Player + Playable: True + Required: True + AllowBots: False + LockFaction: True + Faction: stream + LockSpawn: True + LockTeam: True + Enemies: Enemy + PlayerReference@Enemy: + Name: Enemy + Faction: vinylgod + Enemies: Player + +Actors: + spawn0: mpspawn + Owner: Neutral + Location: 24,30 + TheTranscoder: transcoder + Owner: Player + Location: 24,28 + phub: streamhub + Owner: Player + Location: 20,30 + pnode1: datacentre + Owner: Player + Location: 27,30 + pnode2: datacentre + Owner: Player + Location: 19,26 + ppods: podfarm + Owner: Player + Location: 21,33 + pprint: printfarm + Owner: Player + Location: 27,33 + ptur1: adblocker + Owner: Player + Location: 21,25 + ptur2: adblocker + Owner: Player + Location: 28,26 + pscr: scraper + Owner: Player + Location: 25,25 + pg1: bufferbot + Owner: Player + Location: 23,32 + pg2: bufferbot + Owner: Player + Location: 25,32 + pg3: drmwalker + Owner: Player + Location: 22,27 + StagingCamp: merchtable + Owner: Enemy + Location: 6,6 + scamp2: garage + Owner: Enemy + Location: 9,7 + waxseed0: waxseed + Owner: Neutral + Location: 12,14 + waxseed1: waxseed + Owner: Neutral + Location: 24,24 + waxseed2: waxseed + Owner: Neutral + Location: 36,34 + +Rules: vinylgod|rules/campaign.yaml, rules.yaml + +FluentMessages: vinylgod|fluent/lua.ftl diff --git a/mods/vinylgod/maps/m05/rules.yaml b/mods/vinylgod/maps/m05/rules.yaml new file mode 100644 index 0000000..203ad5d --- /dev/null +++ b/mods/vinylgod/maps/m05/rules.yaml @@ -0,0 +1,5 @@ +world: + LuaScript: + Scripts: campaign.lua, m05.lua + MissionData: + Briefing: A legacy account has noticed the seizure.\n\nYour Transcoder is mid-process on a confiscated master and cannot be moved. Keep it online for six minutes.\n\nThey will arrive in waves. Their engagement is, frankly, remarkable. diff --git a/mods/vinylgod/maps/m06/m06.lua b/mods/vinylgod/maps/m06/m06.lua new file mode 100644 index 0000000..5a95e57 --- /dev/null +++ b/mods/vinylgod/maps/m06/m06.lua @@ -0,0 +1,56 @@ +-- 06: THE SHUFFLE — SIDE B finale. + +Ticks = 0 +CorrectionIssued = false + +WorldLoaded = function() + Stream = Player.GetPlayer("Player") + Party = Player.GetPlayer("Enemy") + InitObjectives(Stream) + + AlgorithmObjective = Stream.AddPrimaryObjective("bring-algorithm-online") + StudioObjective = Stream.AddPrimaryObjective("delete-the-studio") + HubObjective = Stream.AddSecondaryObjective("keep-the-hub-online") + + Trigger.OnKilled(TheStudio, function() + Stream.MarkCompletedObjective(StudioObjective) + end) + + Trigger.OnKilled(TheVan, function() + Trigger.AfterDelay(DateTime.Seconds(2), function() + local hubs = Utils.Where(Map.ActorsInWorld, function(a) + return a.Owner == Stream and a.Type == "streamhub" + end) + if #hubs == 0 then + Stream.MarkFailedObjective(HubObjective) + end + end) + end) +end + +Tick = function() + Ticks = Ticks + 1 + if Ticks % 25 ~= 0 or AlgorithmObjective == nil then + return + end + if Stream.IsObjectiveCompleted(AlgorithmObjective) then + return + end + + local cores = Utils.Where(Map.ActorsInWorld, function(a) + return a.Owner == Stream and a.Type == "algorithm" + end) + if #cores > 0 then + Stream.MarkCompletedObjective(AlgorithmObjective) + if not CorrectionIssued then + CorrectionIssued = true + Media.DisplayMessage(UserInterface.GetFluentMessage("they-are-coming")) + Media.PlaySpeechNotification(Stream, "BaseAttack") + Utils.Do(Map.ActorsInWorld, function(a) + if a.Owner == Party and a.HasProperty("Hunt") then + a.Hunt() + end + end) + end + end +end diff --git a/mods/vinylgod/maps/m06/map.bin b/mods/vinylgod/maps/m06/map.bin new file mode 100644 index 0000000..7fb1f66 Binary files /dev/null and b/mods/vinylgod/maps/m06/map.bin differ diff --git a/mods/vinylgod/maps/m06/map.png b/mods/vinylgod/maps/m06/map.png new file mode 100644 index 0000000..99800b1 Binary files /dev/null and b/mods/vinylgod/maps/m06/map.png differ diff --git a/mods/vinylgod/maps/m06/map.yaml b/mods/vinylgod/maps/m06/map.yaml new file mode 100644 index 0000000..4a6d13e --- /dev/null +++ b/mods/vinylgod/maps/m06/map.yaml @@ -0,0 +1,140 @@ +MapFormat: 12 + +RequiresMod: vinylgod + +Title: 06 - The Shuffle + +Author: Monster Robot Party + +Tileset: VINYLGOD + +MapSize: 66,66 + +Bounds: 1,1,64,64 + +Visibility: MissionSelector + +Categories: Campaign + +LockPreview: True + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Faction: stream + PlayerReference@Player: + Name: Player + Playable: True + Required: True + AllowBots: False + LockFaction: True + Faction: stream + LockSpawn: True + LockTeam: True + Enemies: Enemy + PlayerReference@Enemy: + Name: Enemy + Faction: vinylgod + Bot: wax + Enemies: Player + +Actors: + spawn0: mpspawn + Owner: Neutral + Location: 54,10 + TheVan: streamvan + Owner: Player + Location: 54,10 + scr1: scraper + Owner: Player + Location: 51,11 + scr2: scraper + Owner: Player + Location: 52,13 + pg1: drmwalker + Owner: Player + Location: 49,12 + pg2: bufferbot + Owner: Player + Location: 55,13 + pg3: bufferbot + Owner: Player + Location: 56,13 + TheStudio: mastering + Owner: Enemy + Location: 12,56 + eshop: popupshop + Owner: Enemy + Location: 16,54 + eplant1: pressplant + Owner: Enemy + Location: 20,56 + eplant2: pressplant + Owner: Enemy + Location: 9,51 + eamp1: ampstack + Owner: Enemy + Location: 14,51 + eamp2: ampstack + Owner: Enemy + Location: 18,50 + emerch: merchtable + Owner: Enemy + Location: 19,47 + egarage: garage + Owner: Enemy + Location: 15,46 + eramp: launchramp + Owner: Enemy + Location: 8,46 + etur1: hornpit + Owner: Enemy + Location: 17,43 + etur2: hornpit + Owner: Enemy + Location: 11,44 + etur3: hornpit + Owner: Enemy + Location: 22,52 + emob1: stacktank + Owner: Enemy + Location: 18,42 + emob2: stacktank + Owner: Enemy + Location: 13,41 + emob3: spinner + Owner: Enemy + Location: 20,44 + emob4: djgrunt + Owner: Enemy + Location: 16,41 + emob5: djgrunt + Owner: Enemy + Location: 17,41 + mid1: hornpit + Owner: Enemy + Location: 30,36 + mid2: spinner + Owner: Enemy + Location: 31,34 + waxseed0: waxseed + Owner: Neutral + Location: 12,30 + waxseed1: waxseed + Owner: Neutral + Location: 14,50 + waxseed2: waxseed + Owner: Neutral + Location: 32,32 + waxseed3: waxseed + Owner: Neutral + Location: 50,14 + waxseed4: waxseed + Owner: Neutral + Location: 52,34 + +Rules: vinylgod|rules/campaign.yaml, rules.yaml + +FluentMessages: vinylgod|fluent/lua.ftl diff --git a/mods/vinylgod/maps/m06/rules.yaml b/mods/vinylgod/maps/m06/rules.yaml new file mode 100644 index 0000000..311dcc9 --- /dev/null +++ b/mods/vinylgod/maps/m06/rules.yaml @@ -0,0 +1,5 @@ +world: + LuaScript: + Scripts: campaign.lua, m06.lua + MissionData: + Briefing: Deprecation notice.\n\nThe Party has built a Mastering Studio. The platform classifies this as a competing distribution channel.\n\nBring The Algorithm online and issue the correction. diff --git a/mods/vinylgod/maps/needledrop/map.bin b/mods/vinylgod/maps/needledrop/map.bin index c2f1f4b..a437fcb 100644 Binary files a/mods/vinylgod/maps/needledrop/map.bin and b/mods/vinylgod/maps/needledrop/map.bin differ diff --git a/mods/vinylgod/maps/shellmap/map.bin b/mods/vinylgod/maps/shellmap/map.bin index 0ed6a15..436af8f 100644 Binary files a/mods/vinylgod/maps/shellmap/map.bin and b/mods/vinylgod/maps/shellmap/map.bin differ diff --git a/mods/vinylgod/maps/vinylgod/map.bin b/mods/vinylgod/maps/vinylgod/map.bin index 927feb9..ba68468 100644 Binary files a/mods/vinylgod/maps/vinylgod/map.bin and b/mods/vinylgod/maps/vinylgod/map.bin differ diff --git a/mods/vinylgod/missions/vinylgod.yaml b/mods/vinylgod/missions/vinylgod.yaml index 4fdaab4..9e85de2 100644 --- a/mods/vinylgod/missions/vinylgod.yaml +++ b/mods/vinylgod/missions/vinylgod.yaml @@ -2,3 +2,8 @@ SIDE A - Monster Robot Party: m01 m02 m03 + +SIDE B - The Stream: + m04 + m05 + m06 diff --git a/tools/gen_sideb.py b/tools/gen_sideb.py new file mode 100644 index 0000000..33226ca --- /dev/null +++ b/tools/gen_sideb.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +"""Generate the SIDE B campaign maps (m04-m06) from one shared builder. + +Supersedes the copy-pasted gen_missionN.py approach — mission geometry is data, +not three near-identical scripts. +""" +import os, struct +from PIL import Image + +ROOT = os.path.join(os.path.dirname(__file__), '..') +MAPS = os.path.join(ROOT, 'mods', 'vinylgod', 'maps') + + +def build(name, title, size, wax, actors, player_faction, enemy_faction, + enemy_bot=False, scripts='', briefing=''): + W = H = size + 2 + d = os.path.join(MAPS, name) + os.makedirs(d, exist_ok=True) + + data = bytearray() + data += struct.pack(' [seconds] +set -u +MAP="${1:-arena}" +WAIT="${2:-90}" +DIR="$(cd "$(dirname "$0")/.." && pwd)" +OUT="$(mktemp -t vg_smoke)" + +export PATH="/opt/homebrew/opt/dotnet@8/bin:$PATH" +export DOTNET_ROLL_FORWARD=LatestMajor + +cd "$DIR" || exit 2 +./launch-game.sh Game.Mod=vinylgod "Launch.Map=$MAP" > "$OUT" 2>&1 & + +i=0 +while [ "$i" -lt "$WAIT" ]; do + sleep 3 + i=$((i + 3)) + if grep -qi "Fatal Lua\|Exception of type" "$OUT"; then + echo "FAIL $MAP" + grep -iE "Fatal Lua|Exception of type" "$OUT" | head -3 + pkill -f OpenRA.dll + exit 1 + fi + if grep -q "Game started" "$OUT"; then + sleep 15 + if grep -qi "Fatal Lua\|Exception of type" "$OUT"; then + echo "FAIL $MAP (crashed after start)" + grep -iE "Fatal Lua|Exception of type" "$OUT" | head -3 + pkill -f OpenRA.dll + exit 1 + fi + echo "PASS $MAP (reached gameplay, clean for 15s)" + pkill -f OpenRA.dll + exit 0 + fi +done + +pkill -f OpenRA.dll +echo "DISPLAY_UNAVAILABLE $MAP (never reached gameplay in ${WAIT}s, no crash)" +echo " -> wake the screen and re-run; this is NOT a pass and NOT a code failure" +exit 3