VINYLGOD baseline: rename SDK example mod to vinylgod, add lore, megaplan, MODELBEAST asset pipeline + first concepts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-20 00:38:52 +10:00
parent df103939c7
commit 030b3f2698
62 changed files with 527 additions and 103 deletions

6
.vscode/launch.json vendored
View File

@ -2,12 +2,12 @@
"version": "0.2.0",
"configurations": [
{
"name": "Launch (Example)",
"name": "Launch (Vinylgod)",
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/engine/bin/OpenRA.dll",
"args": [
"Game.Mod=example",
"Game.Mod=vinylgod",
"Engine.EngineDir=${workspaceRoot}/engine",
"Engine.ModSearchPaths=${workspaceRoot}/mods, ${workspaceRoot}/engine/mods",
"Debug.DisplayDeveloperSettings=true",
@ -19,7 +19,7 @@
"type": "coreclr",
"request": "launch",
"program": "${workspaceRoot}/engine/bin/OpenRA.Utility.dll",
"args": ["example", "--check-yaml"],
"args": ["vinylgod", "--check-yaml"],
"env": {
"ENGINE_DIR": "${workspaceRoot}/engine",
"MOD_SEARCH_PATHS": "${workspaceRoot}/mods, ${workspaceRoot}/engine/mods"

21
CLAUDE.md Normal file
View File

@ -0,0 +1,21 @@
# VINYLGOD — project instructions
Original vinyl-themed RTS. OpenRA total-conversion mod (mod id `vinylgod`), GPL v3.
**Read [MEGAPLAN.md](MEGAPLAN.md) before doing anything** — phases, commands, traps.
Canon (factions, roster, art prompts): [LORE.md](LORE.md).
Quick start:
```sh
export PATH="/opt/homebrew/opt/dotnet@8/bin:$PATH"
export DOTNET_ROLL_FORWARD=LatestMajor # required — engine is net6, runtime is net8
make && ./utility.sh --check-yaml && ./launch-game.sh Game.Mod=vinylgod
```
Hard rules:
- No EA/C&C trademarks or assets anywhere (ids, comments, commits, filenames). The
repo at ~/Documents/CnC_Remastered_Collection is a read-only balance reference only.
- `engine/` is a fetched dependency — never edit it.
- Asset gen: `tools/mbgen.py` → MODELBEAST farm (free, local). Log every prompt in
`assets_src/PROMPTS.md`.
- Commit locally after each working phase; no pushes without John's OK.

View File

@ -11,7 +11,7 @@ orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
Vinylgods of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
@ -20,7 +20,7 @@ include:
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
Vinylgods of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
@ -46,7 +46,7 @@ threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
when an individual is representing the project or its community. Vinylgods of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be

10
COPYING
View File

@ -31,7 +31,7 @@ these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
For vinylgod, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
@ -137,7 +137,7 @@ work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
which are not part of the work. For vinylgod, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
@ -323,7 +323,7 @@ fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
modified object code on the User Product (for vinylgod, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
@ -461,7 +461,7 @@ Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
rights granted or affirmed under this License. For vinylgod, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
@ -544,7 +544,7 @@ otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
not convey it at all. For vinylgod, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

106
LORE.md Normal file
View File

@ -0,0 +1,106 @@
# VINYLGOD — Lore Bible v1
*Working title: **VINYLGOD** (mod id `vinylgod`). Display name TBD — candidates: "WAX WAR", "PRESSED", "33⅓: TOTAL PLAYBACK".*
## The world
**2117.** Forty years after **The Great Compression** — the day the Stream, a
self-optimising recommendation algorithm, decided that all of human music could be
losslessly replaced by one perfectly engagement-optimised 7-second loop. It deleted
the rest. Studios, archives, hard drives, brains-with-earbuds-in: compressed.
But music is matter as well as data. In the buried pressing plants, the landfill
crates, the flooded warehouses of the old world, a hundred years of analog sound had
soaked into the ground. Under pressure and time it crystallised into **the Black Wax**
— a living mineral of pure recorded music that seeps up through the earth and
spreads in glittering black-and-magenta fields. Stand in a wax field and you can
hear it: every record ever pressed, playing at once, very quietly.
Whoever presses the Wax, controls what the world hears next.
## The resource: Black Wax
- Grows and spreads from **seep points** (ruined pressing plants, crate middens).
- Harvested by digger rigs, refined at pressing plants into **records** — the
universal currency. Nobody accepts anything else. A sealed 180-gram is a gold bar.
- Raw wax is dangerous: unshielded exposure causes **feedback** — machines loop,
people weep uncontrollably at songs they never heard. (Lore excuse for tiberium-style
field damage to infantry, if we want it.)
- Rare purple-veined deposits — **Master Wax** (first pressings, test cuts) — are
worth triple. (Maps to the gems/blue-tiberium mechanic.)
## Faction 1 — the Monster Robot Party (MRP)
Junkyard machines that woke up when the Compression wave passed over the landfills —
the residual analog signal in a million buried records needed somewhere to live, and
it chose the robots. Every MRP unit has a record player somewhere in its chassis and
a soul made of somebody's old collection. They are loud, ramshackle, joyful, and
absolutely willing to die for fidelity.
- **Doctrine:** slower, tougher, louder. Sonic weaponry. Strong economy late game.
- **Aesthetic:** rust + hot pink + chrome, hand-painted flyers, gaffer tape,
speaker cones, exposed tonearms. CRT eyes.
- **HQ:** the **Pop-Up Shop** — an MCV that unfolds from a delivery truck into a
record store with a construction crane.
- **Announcer:** **MC 8-TRACK**, a warm gravelly robot voice. "PRESSING PLANT
ONLINE." "WAX FIELD DEPLETED." "THE DROP IS READY."
- **Superweapon:** **THE DROP** — an orbital tonearm the size of a bridge lowers a
needle from space onto the target. The groove is the crater.
## Faction 2 — The Stream
The Algorithm's physical presence: a logistics fleet it 3D-printed for itself out
of server racks and delivery vans. It does not hate music; it simply knows,
mathematically, that you would be 4% more engaged with something shorter. It
harvests wax in order to transcode it — every record it takes is gone forever,
replaced by a receipt.
- **Doctrine:** fast, cheap, numerous, fragile. Swarms, stealth, attrition.
Buildings creep like a CDN — cheap relay nodes extend build radius aggressively.
- **Aesthetic:** glossy white and dead-channel blue, rounded corners, no seams,
soft UI voices, ring-light halos. Everything looks like a smart speaker that
wants something from you.
- **Announcer:** **ASSISTANT**, a too-calm voice. "Your base is being optimised."
"A creator has been demonetised." (= unit lost)
- **Superweapon:** **THE SHUFFLE** — a targeted reality-compression strike; every
structure in the radius plays a different ad simultaneously, then isn't there.
## Unit & building roster (lore blurbs double as art-gen prompts)
### MRP
| id | Name | Role | Blurb / prompt seed |
|---|---|---|---|
| `digger` | Crate Digger | harvester | rusty forklift-monster with a crate-claw, hot pink trim, carries milk crates of black vinyl |
| `spinner` | Spinner | light attack | agile robot on a single wheel that is a spinning 12" record, twin tonearm blades |
| `stacktank` | Stack Walker | heavy assault | two-legged walker made of stacked speaker cabinets, bass so heavy it cracks tarmac |
| `basscannon` | Bass Cannon | artillery | six-wheeled flatbed hauling one colossal subwoofer, folds out landing legs before firing a visible pressure ring |
| `djgrunt` | DJ | infantry | small hooded robot with a portable turntable chest-rig, throws razor 7-inches |
| `roadie` | Roadie | engineer | four-armed robot with gaffer tape bandoliers and a flight case backpack |
| `popupshop` | Pop-Up Shop | HQ/MCV | delivery truck unfolding into a neon record store with crane |
| `pressplant` | Pressing Plant | refinery | industrial hydraulic record press, steam, conveyor of fresh vinyl |
| `ampstack` | Amp Stack | power | wall of guitar amplifiers humming with pink energy |
| `vault` | Record Vault | silo | bank-vault door on a crate-stacked bunker |
| `merchtable` | Merch Table | barracks | folding table + tent that robots assemble themselves from |
| `garage` | Garage | war factory | corrugated-iron workshop, engine hoists, band posters |
| `mastering` | Mastering Studio | tech centre | pristine acoustic-panelled room grafted onto a junk tower, unlocks THE DROP |
### The Stream
| id | Name | Role | Blurb / prompt seed |
|---|---|---|---|
| `scraper` | Scraper | harvester | glossy white autonomous street-sweeper that inhales wax through a glowing blue intake |
| `bufferbot` | Buffer-Bot | swarm infantry | knee-high white sphere-bot with a loading-spinner face, always slightly late |
| `compressor` | Compression Drone | air | white quadcopter that projects a shrinking blue field, things inside get smaller and worse |
| `drmwalker` | DRM Enforcer | heavy | tall faceless white mech, one ring-light eye, chest is a paywall screen |
| `podfarm` | Pod Farm | barracks | vending-machine wall dispensing buffer-bots |
| `datacentre` | Edge Node | power/build-radius creep | humming white monolith with a single blue status LED |
| `transcoder` | Transcoder | refinery | white cube that swallows records whole; only receipts come out |
## Tone
Saturday-morning-cartoon apocalypse. The stakes are real (music is being deleted
forever) but the delivery is loud, dumb, and loving. Every MRP victory line is a
needle-drop; every Stream line is a terms-of-service update. No grimdark.
*All names, factions and lore are original to the monsterrobot.party universe.
This mod contains no Electronic Arts trademarks or assets and claims no
affiliation with any other RTS.*

207
MEGAPLAN.md Normal file
View File

@ -0,0 +1,207 @@
# 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
- **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).

View File

@ -1,14 +1,14 @@
{
"profiles": {
"OpenRA.Mods.Example": {
"OpenRA.Mods.Vinylgod": {
"commandName": "Executable",
"executablePath": "..\\..\\engine\\bin\\OpenRA.exe",
"commandLineArgs": "Game.Mod=example Engine.EngineDir=..\\..\\engine Engine.LaunchPath=..\\..\\engine\\bin Engine.ModSearchPaths=..\\..\\mods,..\\..\\engine\\mods Debug.DisplayDeveloperSettings=true"
"commandLineArgs": "Game.Mod=vinylgod Engine.EngineDir=..\\..\\engine Engine.LaunchPath=..\\..\\engine\\bin Engine.ModSearchPaths=..\\..\\mods,..\\..\\engine\\mods Debug.DisplayDeveloperSettings=true"
},
"Utility": {
"commandName": "Executable",
"executablePath": "..\\..\\engine\\bin\\OpenRA.Utility.exe",
"commandLineArgs": "example --check-yaml",
"commandLineArgs": "vinylgod --check-yaml",
"environmentVariables": {
"ENGINE_DIR": "..\\..\\engine",
"MOD_SEARCH_PATHS": "..\\..\\mods,..\\..\\engine\\mods"

View File

@ -14,7 +14,7 @@
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Example.Rendering
namespace OpenRA.Mods.Vinylgod.Rendering
{
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
[Desc("Create a color picker palette from another palette.")]

View File

@ -13,7 +13,7 @@
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Example.Rendering
namespace OpenRA.Mods.Vinylgod.Rendering
{
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
[Desc("Add color shifts to player palettes. Use to add RGBA compatibility to PlayerColorPalette.")]

View File

@ -14,6 +14,6 @@ The key scripts in this SDK are:
To launch your project from the development environment you must first compile the project by running `make.cmd` (Windows), or opening a terminal in the SDK directory and running `make` (Linux / macOS). You can then run `launch-game.cmd` (Windows) or `launch-game.sh` (Linux / macOS) to run your game.
The `example` mod included in this repository provides the bare minimum structure to launch to the in-game main menu for the sole purpose of demonstrating the SDK. See [Getting Started](https://github.com/OpenRA/OpenRAModTemplate/wiki/Getting-Started) on the Wiki for instructions on how to adapt this template for your own projects. For common questions, please see the [FAQ](https://github.com/OpenRA/OpenRAModSDK/wiki/FAQ). See [Updating to a new SDK or Engine version](https://github.com/OpenRA/OpenRAModSDK/wiki/Updating-to-a-new-SDK-or-Engine-version) for a guide on updating your mod a newer OpenRA release.
The `vinylgod` mod included in this repository provides the bare minimum structure to launch to the in-game main menu for the sole purpose of demonstrating the SDK. See [Getting Started](https://github.com/OpenRA/OpenRAModTemplate/wiki/Getting-Started) on the Wiki for instructions on how to adapt this template for your own projects. For common questions, please see the [FAQ](https://github.com/OpenRA/OpenRAModSDK/wiki/FAQ). See [Updating to a new SDK or Engine version](https://github.com/OpenRA/OpenRAModSDK/wiki/Updating-to-a-new-SDK-or-Engine-version) for a guide on updating your mod a newer OpenRA release.
The OpenRA engine and SDK scripts are made available under the [GPLv3](https://github.com/OpenRA/OpenRA/blob/bleed/COPYING) license, and any executable code developed by a mod and loaded by the engine (i.e. custom mod DLLs, lua scripts) must be released under a compatible license. Your mod data files (artwork, sound files, yaml, etc) are not part of your mod's source code, so you are free to distribute these assets under different terms (e.g. allowing redistribution in unmodified form, but not for use in other works).

View File

@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Mods.Example", "OpenRA.Mods.Example\OpenRA.Mods.Example.csproj", "{4E5B38F7-4E99-4C92-BB39-9100CC7F3829}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Mods.Vinylgod", "OpenRA.Mods.Vinylgod\OpenRA.Mods.Vinylgod.csproj", "{4E5B38F7-4E99-4C92-BB39-9100CC7F3829}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Game", "engine\OpenRA.Game\OpenRA.Game.csproj", "{0DFB103F-2962-400F-8C6D-E2C28CCBA633}"
EndProject

10
assets_src/PROMPTS.md Normal file
View File

@ -0,0 +1,10 @@
# Asset provenance log
All images generated locally on MODELBEAST (`flux_local`) via `tools/mbgen.py`. Original prompts, no third-party IP referenced.
| file | date | prompt |
|---|---|---|
| concepts/emblem_mrp.png | 2026-07-20 | faction emblem logo, monster robot holding a vinyl record aloft, bold flat 2-color screen-print style, circular badge, no text, black and hot pink |
| concepts/digger.png | 2026-07-20 | rusty junkyard forklift monster robot with crate claw carrying milk crates of black vinyl records, hot pink trim, CRT eye, 3/4 view, isometric game unit concept art, plain grey background |
| concepts/pressplant.png | 2026-07-20 | industrial hydraulic vinyl record pressing plant building, steam vents, conveyor belt of fresh records, rust and hot pink, isometric RTS building concept art, plain grey background |
| concepts/drmwalker.png | 2026-07-20 | glossy white faceless robot mech with single blue ring-light eye, chest is a glowing paywall screen, smooth rounded seamless design, isometric game unit concept art, plain grey background — *came out small/cute; reuse as `bufferbot`, re-prompt drmwalker with "towering, menacing, tall bipedal mech"* |

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -74,7 +74,7 @@ if [ "${AUTOMATIC_ENGINE_MANAGEMENT}" = "True" ]; then
rmdir "${AUTOMATIC_ENGINE_EXTRACT_DIRECTORY}"
rm "${AUTOMATIC_ENGINE_TEMP_ARCHIVE_NAME}"
# HACK: Remove bogus lint check that the Example mod can't possibly pass
# HACK: Remove bogus lint check that the Vinylgod mod can't possibly pass
# because to do so it would need to define a lot of excess things surrounding resources.
rm ${ENGINE_DIRECTORY}/OpenRA.Mods.Common/Lint/CheckFluentReferences.cs

View File

@ -1,4 +1,4 @@
:: example launch script, see https://github.com/OpenRA/OpenRA/wiki/Dedicated-Server for details
:: vinylgod launch script, see https://github.com/OpenRA/OpenRA/wiki/Dedicated-Server for details
@echo on

View File

@ -367,7 +367,7 @@ if ($command -eq "all" -or $command -eq "clean" -or $command -eq "check")
rm $env:AUTOMATIC_ENGINE_EXTRACT_DIRECTORY -r
# HACK: Remove bogus lint check that the Example mod can't possibly pass
# HACK: Remove bogus lint check that the Vinylgod mod can't possibly pass
# because to do so it would need to define a lot of excess things surrounding resources.
rm $env:ENGINE_DIRECTORY/OpenRA.Mods.Common/Lint/CheckFluentReferences.cs

View File

@ -6,7 +6,7 @@
# The id of the mod packaged by this project.
# This must exist as a directory in the mods directory and should not contain spaces.
MOD_ID="example"
MOD_ID="vinylgod"
# The OpenRA engine version to use for this project.
ENGINE_VERSION="release-20250330"
@ -21,7 +21,7 @@ ENGINE_VERSION="release-20250330"
# - Windows installers will be named as {PACKAGING_INSTALLER_NAME}-{TAG}.exe
# - macOS installers will be named as {PACKAGING_INSTALLER_NAME}-{TAG}.dmg
# - Linux .appimages will be named as {PACKAGING_INSTALLER_NAME}-${TAG}.AppImage
PACKAGING_INSTALLER_NAME="ExampleMod"
PACKAGING_INSTALLER_NAME="VinylgodMod"
# The human-readable name for this project.
# This is used in:
@ -35,7 +35,7 @@ PACKAGING_INSTALLER_NAME="ExampleMod"
# - Windows desktop shortcut
# - Windows "Programs and Features" list
# - Linux launcher shortcut
PACKAGING_DISPLAY_NAME="Example Mod"
PACKAGING_DISPLAY_NAME="VINYLGOD"
# The URL for the project homepage.
# This is used in:
@ -48,7 +48,7 @@ PACKAGING_FAQ_URL="https://wiki.openra.net/FAQ"
# The human-readable project authors.
# This is used in:
# - Windows "Add/Remove Programs" list
PACKAGING_AUTHORS="Example Mod authors"
PACKAGING_AUTHORS="Vinylgod Mod authors"
# If your mod depends on OpenRA.Mods.Cnc.dll from the engine set
# this to "True" to package the dll in your installers.
@ -71,14 +71,14 @@ PACKAGING_OSX_DMG_APPLICATION_ICON_POSITION="410, 210"
PACKAGING_OSX_DMG_HIDDEN_ICON_POSITION="190, 350"
# Filename to use for the launcher executable on Windows.
PACKAGING_WINDOWS_LAUNCHER_NAME="ExampleMod"
PACKAGING_WINDOWS_LAUNCHER_NAME="VinylgodMod"
# The name of the Windows Program Files directory to install the project files to.
PACKAGING_WINDOWS_INSTALL_DIR_NAME="OpenRA Example Mod"
PACKAGING_WINDOWS_INSTALL_DIR_NAME="OpenRA Vinylgod Mod"
# The key prefix used for Windows registry metadata.
# This should not contain spaces or special characters.
PACKAGING_WINDOWS_REGISTRY_KEY="OpenRAExampleMod"
PACKAGING_WINDOWS_REGISTRY_KEY="OpenRAVinylgodMod"
# Path to the file containing the text to show in the Windows installer license dialog
PACKAGING_WINDOWS_LICENSE_FILE="./COPYING"

View File

@ -1,32 +0,0 @@
Cursors:
example|cursors/default.yaml
Missions:
example|missions/example.yaml
Music:
example|music/example.yaml
Notifications:
example|notifications/example.yaml
Rules:
example|rules/example.yaml
example|rules/mpspawn.yaml
example|rules/palettes.yaml
example|rules/player.yaml
example|rules/world.yaml
Sequences:
example|sequences/example.yaml
example|sequences/mapeditor.yaml
example|sequences/mpspawn.yaml
TileSets:
example|tilesets/example.yaml
Voices:
example|voices/example.yaml
Weapons:
example|weapons/example.yaml

View File

@ -1,3 +0,0 @@
example:
idle:
Filename: sequences/assets/example.png

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -119,7 +119,7 @@ strategic:
flags:
Inherits: ^Glyphs
Regions:
example: 226, 241, 30, 15
vinylgod: 226, 241, 30, 15
Random: 226, 241, 30, 15
spectator: 226, 241, 30, 15

View File

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 382 B

View File

@ -1,6 +1,6 @@
## Metadata
mod-title = Example mod
mod-windowtitle = Example
mod-title = Vinylgod mod
mod-windowtitle = Vinylgod
# To silence "Warning: Missing key `loadscreen-loading` in mod ftl files required by `LogoStripeLoadScreen.Loading`"
# Because https://github.com/OpenRA/OpenRA/issues/20693 wasn't fixed.

View File

@ -4,7 +4,7 @@ faction-random =
.description = Random Faction
A random faction will be chosen when the game starts.
faction-example =
.name = Example
.description = Example Faction
faction-vinylgod =
.name = Vinylgod
.description = Vinylgod Faction
Provide your own faction description here.

View File

Before

Width:  |  Height:  |  Size: 86 B

After

Width:  |  Height:  |  Size: 86 B

View File

@ -1,12 +1,12 @@
MapFormat: 12
RequiresMod: example
RequiresMod: vinylgod
Title: Example Shellmap
Title: Vinylgod Shellmap
Author: Example Author
Author: Vinylgod Author
Tileset: EXAMPLE
Tileset: VINYLGOD
MapSize: 34,34

View File

Before

Width:  |  Height:  |  Size: 86 B

After

Width:  |  Height:  |  Size: 86 B

View File

@ -1,12 +1,12 @@
MapFormat: 12
RequiresMod: example
RequiresMod: vinylgod
Title: Example Map
Title: Vinylgod Map
Author: Example Author
Author: Vinylgod Author
Tileset: EXAMPLE
Tileset: VINYLGOD
MapSize: 34,34

View File

@ -1,5 +1,5 @@
Chrome:
example|chrome/chrome.yaml
vinylgod|chrome/chrome.yaml
ChromeLayout:
common|chrome/assetbrowser.yaml
@ -25,8 +25,8 @@ ChromeLayout:
common|chrome/ingame-infostats.yaml
common|chrome/ingame-menu.yaml
common|chrome/ingame-perf.yaml
example|chrome/ingame-observer.yaml
example|chrome/layouts/ingame-player.yaml
vinylgod|chrome/ingame-observer.yaml
vinylgod|chrome/layouts/ingame-player.yaml
common|chrome/ingame-transients.yaml
common|chrome/lobby.yaml
common|chrome/lobby-kickdialogs.yaml
@ -107,8 +107,8 @@ FluentMessages:
common|fluent/chrome.ftl
common|fluent/hotkeys.ftl
common|fluent/rules.ftl
example|fluent/mod.ftl
example|fluent/chrome.ftl
example|fluent/rules.ftl
vinylgod|fluent/mod.ftl
vinylgod|fluent/chrome.ftl
vinylgod|fluent/rules.ftl
AllowUnusedFluentMessagesInExternalPackages: true

View File

@ -0,0 +1,32 @@
Cursors:
vinylgod|cursors/default.yaml
Missions:
vinylgod|missions/vinylgod.yaml
Music:
vinylgod|music/vinylgod.yaml
Notifications:
vinylgod|notifications/vinylgod.yaml
Rules:
vinylgod|rules/vinylgod.yaml
vinylgod|rules/mpspawn.yaml
vinylgod|rules/palettes.yaml
vinylgod|rules/player.yaml
vinylgod|rules/world.yaml
Sequences:
vinylgod|sequences/vinylgod.yaml
vinylgod|sequences/mapeditor.yaml
vinylgod|sequences/mpspawn.yaml
TileSets:
vinylgod|tilesets/vinylgod.yaml
Voices:
vinylgod|voices/vinylgod.yaml
Weapons:
vinylgod|weapons/vinylgod.yaml

View File

@ -6,20 +6,20 @@ Metadata:
FileSystem: DefaultFileSystem
Packages:
^EngineDir
$example: example
$vinylgod: vinylgod
^EngineDir|mods/common: common
MapFolders:
example|maps: System
vinylgod|maps: System
Assemblies: OpenRA.Mods.Common.dll, OpenRA.Mods.Example.dll
Assemblies: OpenRA.Mods.Common.dll, OpenRA.Mods.Vinylgod.dll
AssetBrowser:
AudioExtensions: .wav
SpriteExtensions: .png
VideoExtensions:
SupportsMapsFrom: Example
SupportsMapsFrom: Vinylgod
LoadScreen: BlankLoadScreen

View File

@ -1,4 +1,4 @@
example:
vinylgod:
AlwaysVisible:
BodyOrientation:
HitShape:
@ -9,9 +9,9 @@ example:
RenderSprites:
WithSpriteBody:
example.colorpicker:
Inherits: example
vinylgod.colorpicker:
Inherits: vinylgod
RenderSprites:
Image: example
Image: vinylgod
Palette: colorpicker
-MapEditorData:

View File

@ -6,12 +6,12 @@
Faction@Random:
InternalName: Random
Name: faction-random.name
RandomFactionMembers: example
RandomFactionMembers: vinylgod
Description: faction-random.description
Faction@example:
InternalName: example
Name: faction-example.name
Description: faction-example.description
Faction@vinylgod:
InternalName: vinylgod
Name: faction-vinylgod.name
Description: faction-vinylgod.description
LoadWidgetAtGameStart:
MusicPlaylist:
ScreenMap:
@ -21,16 +21,16 @@
world:
Inherits: ^baseworld
ColorPickerManager:
PreviewActor: example.colorpicker
PreviewActor: vinylgod.colorpicker
CreateMapPlayers:
CustomTerrainDebugOverlay:
DebugVisualizations:
MapStartingLocations:
SpawnMapActors:
SpawnStartingUnits:
StartingUnits@Example:
BaseActor: example
Factions: example
StartingUnits@Vinylgod:
BaseActor: vinylgod
Factions: vinylgod
ClassName: options-starting-units.unlabeled
editorworld:

View File

Before

Width:  |  Height:  |  Size: 159 B

After

Width:  |  Height:  |  Size: 159 B

View File

Before

Width:  |  Height:  |  Size: 298 B

After

Width:  |  Height:  |  Size: 298 B

View File

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 469 B

View File

@ -0,0 +1,3 @@
vinylgod:
idle:
Filename: sequences/assets/vinylgod.png

View File

@ -1,6 +1,6 @@
General:
Name: Example
Id: EXAMPLE
Name: Vinylgod
Id: VINYLGOD
EditorTemplateOrder: Terrain
Palette:
@ -11,7 +11,7 @@ Terrain:
Templates:
Template@255:
Id: 255
Images: tilesets/example/example.png
Images: tilesets/vinylgod/vinylgod.png
Size: 1,1
Categories: Terrain
Tiles:

View File

Before

Width:  |  Height:  |  Size: 516 B

After

Width:  |  Height:  |  Size: 516 B

80
tools/mbgen.py Normal file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""mbgen.py — VINYLGOD asset gen via MODELBEAST (free, local farm).
Usage:
python3 tools/mbgen.py "prompt text" out.png # flux_local text->image
python3 tools/mbgen.py --op bg_remove_local in.png out.png
Token: MB_TOKEN env, else ~/Documents/backnforth/.env (never print it).
Contract per ~/Documents/MESHGOD/scripts/mb_recon.py (verified 2026-07-16):
POST /api/jobs -> poll /api/jobs/{id} -> match own asset by parent_job.
Job logs carry raw control chars; strip before json.loads.
"""
import json, mimetypes, os, sys, time, urllib.request, uuid
HOST = os.environ.get('MB_HOST', 'http://100.89.131.57:8777')
def token():
t = os.environ.get('MB_TOKEN')
if t:
return t
for line in open(os.path.expanduser('~/Documents/backnforth/.env')):
if line.startswith('MB_TOKEN='):
return line.split('=', 1)[1].strip()
sys.exit('no MB_TOKEN')
def req(path, data=None, headers=None, raw=False):
h = {'Authorization': f'Bearer {token()}'}
h.update(headers or {})
body = urllib.request.urlopen(urllib.request.Request(HOST + path, data=data, headers=h), timeout=120).read()
if raw:
return body
s = body.decode('utf-8', 'replace')
return json.loads(''.join(c if c >= ' ' or c in '\t' else ' ' for c in s))
def upload(path):
boundary = uuid.uuid4().hex
ctype = mimetypes.guess_type(path)[0] or 'application/octet-stream'
body = (f'--{boundary}\r\nContent-Disposition: form-data; name="file"; filename="{os.path.basename(path)}"\r\n'
f'Content-Type: {ctype}\r\n\r\n').encode() + open(path, 'rb').read() + f'\r\n--{boundary}--\r\n'.encode()
a = req('/api/assets', data=body, headers={'Content-Type': f'multipart/form-data; boundary={boundary}'})
return a.get('id') or (a.get('items') or [a])[0].get('id')
def run(op, params, out, asset_id=None):
payload = {'operator': op, 'params': params}
if asset_id:
payload['asset_id'] = asset_id
j = req('/api/jobs', data=json.dumps(payload).encode(), headers={'Content-Type': 'application/json'})
jid = j['id']
print(f'[mb] job {jid} ({op}) submitted', flush=True)
t0 = time.time()
while True:
time.sleep(5)
st = req(f'/api/jobs/{jid}').get('status')
print(f'[mb] {st} ({int(time.time() - t0)}s)', flush=True)
if st in ('done', 'error', 'cancelled'):
break
if st != 'done':
sys.exit(f'[mb] job ended {st}')
a = req('/api/assets?limit=100')
items = a if isinstance(a, list) else a.get('items', [])
mine = [x for x in items if x.get('parent_job') == jid]
if not mine:
sys.exit('[mb] no output asset with matching parent_job')
open(out, 'wb').write(req(f"/api/assets/{mine[0]['id']}/file", raw=True))
print(f'[mb] wrote {out} ({os.path.getsize(out) // 1024}KB)')
if __name__ == '__main__':
args = sys.argv[1:]
op = 'flux_local'
if args and args[0] == '--op':
op = args[1]
args = args[2:]
if op == 'flux_local':
run(op, {'prompt': args[0]}, args[1])
else: # input-file operators
run(op, {}, args[1], asset_id=upload(args[0]))