# VINYLGOD MEGAPLAN — execution instructions Audience: an executing coding agent (Opus 4.8). Read this whole file before phase 1. Lore, names, and art prompts live in [LORE.md](LORE.md) — use it as canon. ## 0. Mission & hard rules Build **VINYLGOD**, an original vinyl-themed RTS, as an OpenRA total-conversion mod in this repo. The classic RTS at `~/Documents/CnC_Remastered_Collection` is the **read-only design reference** for balance and feel. Hard rules: 1. **Never** copy code, text, names, or assets from the reference repo into this one. No EA trademarks anywhere — not in ids, comments, commit messages, or filenames. Balance *numbers* (costs, HP, ranges) are facts and fine to reuse. 2. This repo is GPL v3 (OpenRA engine + SDK). Everything we add stays GPL-compatible. All art/audio we generate on MODELBEAST is our own — keep provenance: every asset in `assets_src/` comes from a prompt logged in `assets_src/PROMPTS.md` (append as you go). 3. Data-driven first. YAML in `mods/vinylgod/` solves 95% of everything. Only write C# traits in `OpenRA.Mods.Vinylgod/` when no stock trait combination works, and check `engine/OpenRA.Mods.Common/Traits/` thoroughly first. 4. After every phase: run the checks in §2, commit with a one-line message. Don't push anywhere without John's say-so. ## 1. Environment (ultra, macOS arm64) ```sh cd ~/Documents/VINYLGOD export PATH="/opt/homebrew/opt/dotnet@8/bin:$PATH" export DOTNET_ROLL_FORWARD=LatestMajor # engine targets net6, we run net8 — required make # build engine (cached) + mod dll ./utility.sh --check-yaml # lint all mod yaml — must stay clean ./launch-game.sh Game.Mod=vinylgod # run the game ./launch-game.sh Game.Mod=vinylgod Launch.Benchmark=true # headless-ish smoke test ``` - Engine source lives in `./engine` (auto-fetched, treat read-only; version pinned in `mod.config` `ENGINE_VERSION="release-20250330"`). - Logs: `~/Library/Application Support/OpenRA/Logs/` (`debug.log`, `exception.log`). - **The single best reference is `engine/mods/ra` and `engine/mods/cnc`** — complete, working mods using the same engine version. When unsure how to wire a trait, find the equivalent actor there and adapt. (Referencing their YAML *structure* is what they're for; don't ship their art/strings.) ## 2. Definition of done (every phase) 1. `./utility.sh --check-yaml` → zero errors. 2. `make` → zero errors. 3. Launch the game, start a skirmish on the test map, exercise the new feature. 4. `git add -A && git commit` (local only). ## 3. Repo layout (what the SDK gives us) ``` mod.config # MOD_ID=vinylgod, packaging names mods/vinylgod/ mod.yaml # manifest: packages, rules/weapons/sequences includes rules/ # actor definitions (vinylgod.yaml, world.yaml, ...) weapons/ # weapon/warhead definitions sequences/ # sprite-name → image-sheet mappings tilesets/ # terrain definition maps/vinylgod/, maps/shellmap/ chrome/, fluent/ # UI layout, user-facing strings (fluent = translations) notifications/, voices/, music/ OpenRA.Mods.Vinylgod/ # C# project for custom traits (mostly empty — keep it so) tools/mbgen.py # MODELBEAST asset generation (works, tested) assets_src/ # generated art sources (concepts, renders, sheets) LORE.md # canon: factions, roster, art prompt seeds ``` The current mod content is the SDK example (one test unit, one tileset) renamed to `vinylgod` — it boots. You will progressively replace it. ## 4. Balance bible (verified from the reference codebase) Anchor numbers, lifted from the reference RTS's data tables (its harvester block and building cost table were read directly from source). Use as starting values, tune later: | Thing | Reference value | VINYLGOD actor | |---|---|---| | Harvester | cost 1400, HP 600, slow, unarmed, prereq refinery | `digger` | | Refinery | cost 2000 | `pressplant` | | Power plant | cost 300 / adv 700 | `ampstack` | | Silo | cost 150 | `vault` | | Barracks | cost 300 | `merchtable` / `podfarm` | | War factory | cost 2000 | `garage` | | Construction yard | cost 5000 (deploys from MCV) | `popupshop` | | Basic infantry | cost 100 | `djgrunt` / `bufferbot` | | Medium tank | cost 800 | `stacktank` | | Artillery | cost 450, long range, fragile | `basscannon` | | Full harvester load | ~700 credits | one digger load of wax | | Tech centre | cost 1500, unlocks superweapon | `mastering` | Design intent per faction (from LORE.md): MRP = fewer/tougher/louder, The Stream = cheap/fast/swarm with aggressive build-radius creep via `datacentre`. ## 5. Phases ### Phase A — Wax economy (do this first) Goal: skirmish map where a `digger` harvests wax fields and returns it to a `pressplant` for credits; `ampstack` powers the base; `vault` extends storage. 1. **Resource type.** In `rules/world.yaml` add to the `World` actor: `ResourceLayer` + `ResourceRenderer` (crib the exact block from `engine/mods/ra/rules/world.yaml`, resource type id `wax`, value ~25/cell-step), and a `ResourceClaimLayer` for harvester coordination. Player side: `PlayerResources` (on `Player` actor in `rules/player.yaml`) with `DefaultCash: 5000`. 2. **Wax overlay art.** Placeholder first: 12 density frames of magenta-black blobs, 24×24px, one PNG sheet; wire in `sequences/`. Density growth = `ResourceLayer` handles it; seed patches in the test map with the map editor (`./launch-game.sh Game.Mod=vinylgod` → map editor is built into the game menu). 3. **Actors.** In `rules/vinylgod.yaml` define `digger` (traits: `Harvester` with `Resources: wax`, `Mobile`, `Health`, `Selectable`, `DockClientManager`), `pressplant` (`Refinery`, `DockHost`, `Building`), `ampstack` (`Power: 100`), `vault` (`StoresPlayerResources`). Copy trait shapes from the RA mod's equivalents and rename everything. 4. **Placeholder sprites** are fine (single-frame colored boxes); art comes in Phase D. Sequences yaml must exist for every actor or check-yaml fails. 5. Acceptance: start skirmish, build nothing, select pre-placed digger → it auto-harvests, credits tick up on delivery. ### Phase B — Building & production Goal: full base loop. `popupshop` MCV deploys into construction yard; sidebar builds `ampstack`/`pressplant`/`merchtable`/`garage`/`vault`; `garage` produces `digger` and `spinner`; `merchtable` produces `djgrunt` and `roadie`. Key traits: `Transforms` (MCV→HQ), `BaseBuilding`, `ProductionQueue` (on Player, one per queue type: Building, Vehicle, Infantry), `Production` + `Exit` on factories, `Buildable` (with `Prerequisites:` and `Queue:`) on everything, `ProvidesPrerequisite` on tech buildings, `GivesBuildableArea` + `RequiresBuildableArea` for build radius. Sidebar chrome: adapt `chrome.yaml` from the example mod's working sidebar; icons are 64×48 PNGs (placeholder text-on-color fine). Prerequisite chain (mirrors the reference game's feel): `ampstack → pressplant → merchtable → garage; pressplant → vault; garage+pressplant → mastering`. ### Phase C — Combat & first playable faction (MRP) Goal: winnable skirmish vs a second human (no AI yet). 1. Weapons in `weapons/`: `tonearm_blade` (anti-inf melee-ish), `seven_inch` (thrown razor record, arcing), `bass_ring` (artillery AoE, `Warhead@Concussion`), `feedback_burst` (anti-armor). Copy warhead structure from RA weapons yaml. 2. Combat units: `spinner` (fast, cheap, `Armament` tonearm_blade), `stacktank` (slow, tough, bass), `basscannon` (long range, min range, fragile), `djgrunt` (basic inf), `roadie` (`EngineerRepair`, `Captures`). 3. `AttackMove`, stances, `AutoTarget` on everything armed. 4. Win condition: `MissionObjectives` + `ConquestVictoryConditions` on World. 5. Acceptance: two-player LAN skirmish (`launch-dedicated.sh` + two clients, or one client + `Launch.Connect`), fight to elimination. ### Phase D — Real art via MODELBEAST (can interleave with B/C) Pipeline per unit (all free, all local; ~10s/image, minutes/mesh): 1. `python3 tools/mbgen.py ", isometric game unit, plain grey background" assets_src/concepts/.png` 2. For units needing rotation facings: concept → GLB via `python3 tools/mbgen.py --op trellis_mac assets_src/concepts/.png assets_src/mesh/.glb` (or `hunyuan3d_mlx`), then Blender (ultra has it + MCP) renders a turntable: 16 facings for vehicles, 8 for infantry, 1 for buildings + a 'make' anim later. Orthographic camera pitched ~40°, 1-2px outline, render 96×96 (vehicles) / 64×64 (infantry) / 192×192 (buildings) on transparency. 3. Montage frames into a horizontal strip PNG (ImageMagick `montage -tile x1`), drop in `mods/vinylgod/sequences/assets/`, reference from sequences yaml (`Filename:`, `Facings: 16`, `Frames:`). OpenRA loads plain PNG sheets directly. 4. Buildings: damaged state = second row (50% HP swap via sequence `damaged-idle`). 5. Log every prompt in `assets_src/PROMPTS.md`. 6. Shadows: `Shadow` render trait or baked — pick one, be consistent. Sprite QA gate: consistent camera angle + light direction across ALL units of a faction, or it reads as clip-art soup. Fix the Blender camera rig once, reuse. ### Phase E — The Stream faction + AI 1. Second side in `rules/player.yaml` `Factions`; Stream actor set per LORE.md roster; shared tech tree structure, different stats (cheap/fast/fragile). 2. Skirmish AI: `ModularBot` + `SquadManagerBotModule` + `BaseBuilderBotModule` + `HarvesterBotModule` on the Player — copy the RA mod's bot config wholesale and re-point unit ids, then tune. 3. Acceptance: beat the bot; watch the bot expand, harvest, and attack. ### Phase F — Sound & polish 1. Music: `mbgen.py --op music_local` (tags → track; primary-only, ~4min cold start per job) — 4-6 tracks, wire in `music/`. 2. MC 8-TRACK announcer: `tts_local` (pick a gravelly Kokoro voice) or `voice_clone_local` with a reference wav from `~/Documents/character_kit/voices/`. Lines in `notifications/` yaml. ASSISTANT voice for Stream: cleanest TTS voice, dry. 3. Shellmap: scripted background battle on the menu screen (see RA shellmap lua). 4. Chrome re-skin: hot pink/black UI, emblem (`assets_src/concepts/emblem_mrp.png`) as mod logo in `mod.yaml` + loadscreen. 5. Rename pass: pick final display name (see LORE.md candidates), update `mod.config` packaging fields; `packaging/` scripts already produce a mac .dmg. ### Phase G — Ship 1. Run the `ship-check` skill before anything goes public. 2. Gitea remote first (`ssh://git@100.71.119.27:222`), public GitHub mirror when John says go (repo must carry COPYING = GPL v3, credit OpenRA engine). 3. itch.io page + dmg/AppImage/exe via `packaging/package-all.sh `. ## 6. Known traps 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. - 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`). - MODELBEAST `music_local` takes `prompt` (not `tags`); `tts_local` takes `text`. - `ConquestVictoryConditions` needs `MapOptions:` on the world actor; `Health` needs `HitShape`; actors need a visibility trait (`HiddenUnderFog:`); `Selectable` already includes `Interactable` (don't add both). - `StartingUnits.SupportActors` must be mobile units, never buildings. - In zsh scripts: `UID` is a readonly shell variable, and an unmatched glob aborts the whole command line (`setopt nonomatch` first). - **Forgot `DOTNET_ROLL_FORWARD=LatestMajor`** → "framework 6.0.0 not found" crash. - Every actor referenced in a map/rules needs a sequences entry, even placeholders. - Trait names change between engine releases — trust `engine/mods/ra` on THIS checkout over any wiki/tutorial (wiki tracks bleed). - The Fluent string files (`fluent/*.ftl`) must contain every `label`/`name` key you reference or check-yaml warns; keep them updated as actors land. - MODELBEAST guest token caps at 4 active jobs — batch sequentially; if we need bulk parallel gen, mint a proper user token on m3ultra (John approved) — the token-mint path is in the MODELBEAST repo on m3ultra (`~/Documents/MODELBEAST`). - Don't touch `engine/` — engine mods belong upstream or via `AUTOMATIC_ENGINE_MANAGEMENT=False` (avoid).