219 lines
13 KiB
Markdown
219 lines
13 KiB
Markdown
# 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 "<LORE.md prompt seed>, isometric game unit, plain grey background" assets_src/concepts/<id>.png`
|
||
2. For units needing rotation facings: concept → GLB via
|
||
`python3 tools/mbgen.py --op trellis_mac assets_src/concepts/<id>.png assets_src/mesh/<id>.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 <tag>`.
|
||
|
||
## 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).
|