Skip to content

Add headless, scriptable Random Map Generation (RMG) CLI mode#322

Closed
BlackgamerzVN wants to merge 4 commits into
CnCNet:masterfrom
BlackgamerzVN:blackgamerzvn-automatic-bassoon
Closed

Add headless, scriptable Random Map Generation (RMG) CLI mode#322
BlackgamerzVN wants to merge 4 commits into
CnCNet:masterfrom
BlackgamerzVN:blackgamerzvn-automatic-bassoon

Conversation

@BlackgamerzVN

@BlackgamerzVN BlackgamerzVN commented Jul 9, 2026

Copy link
Copy Markdown

⚠️ NOT BUILD-VERIFIED — READ BEFORE MERGING ⚠️

This PR was developed in a sandboxed environment with no network access to nuget.org, and the sandbox's global NuGet config also has a stale, unrelated local source (C:\INI Parser) that breaks dotnet restore/dotnet build before the real dependencies can even be resolved. This was confirmed independently in two separate sandboxes (this session and the collaborating client-side session), and the C:\INI Parser path has zero references anywhere in this repo (grepped) — it's leftover machine cruft, not a project dependency.

As a result, none of the code in this PR has been compiled or run. All of it was written and cross-checked manually against existing call sites, signatures, and types in the repo (MapSetup.cs, TerrainGeneratorConfigWindow.cs, TerrainGenerationMutation.cs, Map.cs, MapWriter.cs, Theater.cs, etc.), but that is not a substitute for a real build + smoke test.

Before merging, someone with a working build environment should:

  1. dotnet build the solution and fix any compile errors.
  2. Run a real smoke test, e.g.:
    WorldAlteringEditor.exe --generate-map --game-directory "<path to a TS/RA2/YR install>" --theater Temperate --width 100 --height 100 --players 4 --seed 12345 --output C:\temp\test.map
    
    and confirm it exits 0, writes a valid .map file, and that the file loads correctly back in the normal WAE GUI (terrain looks sane, player waypoints exist, resource overlays are present and don't overlap water/roads).
  3. Sanity-check both --symmetry rotational (default) and --symmetry mirror for a few player counts (2, 3, 4).
  4. Sanity-check --algorithm wfc (see below) against the default --algorithm scatter, ideally on a theater with a terrain generator preset that has 2+ tile groups (so WFC actually has multiple domains to work with).

Risk / verification status by piece, so reviewers know what's what

Piece Status
Headless CLI plumbing (--generate-map, hidden window, arg parsing) Core to this PR's purpose; same age/maturity as everything else here — unverified, but a straightforward, contained change (Program.cs / GameClass.cs).
--algorithm scatter (default) Directly reuses the existing, already-shipped TerrainGenerationMutation engine exactly as the GUI's Terrain Generator tool calls it, just over the whole map instead of a brush selection. Lowest risk of the terrain paths — closest to "known-good code, new caller."
Symmetry (rotational/mirror) + wedge-balanced resources New geometry/math logic, moderate risk, but self-contained (no changes to existing mutation/rendering code paths).
--algorithm wfc (opt-in, non-default) Newest and highest-risk piece. A brand-new from-scratch algorithm (WaveFunctionCollapseSolver.cs) with no prior art in this codebase to cross-check signatures against — only manually reasoned through, not just manually diffed against existing patterns. Strongly recommend reviewing/testing this path last and separately from the rest, and it's safe to ship the rest without it if it needs more iteration (it's fully opt-in; default behavior is untouched).

What this adds

A new --generate-map headless CLI mode so an external process (e.g. a CnCNet game lobby client) can invoke WAE non-interactively to produce a randomized, playable .map file — no visible window, structured stdout/exit-code contract for automation.

Why a hidden window instead of a pure console app

TheaterGraphics allocates real Texture2D GPU objects in its constructor, so loading theater tile/overlay/smudge data requires a live MonoGame GraphicsDevice — this is true even outside of rendering. Decoupling that is a much bigger refactor with no real benefit for this use case, so this PR keeps a real MonoGame window but never shows/activates it, and skips MainMenu/all interactive UI when --generate-map is passed.

What it generates

  • A blank map of the requested theater/size (Map.InitNew, same path as the GUI's "new map" flow).
  • Terrain variety via one of two selectable algorithms (--algorithm, see below and the risk table above), both built on the existing TerrainGenerationMutation engine (previously only ever driven by a user-brushed selection) through a new lightweight non-UI IMutationTarget stub (HeadlessMutationTarget).
  • Symmetric player start Waypoints (0..N-1), with a choice of:
    • Rotational (default): N-fold rotational symmetry around the map center.
    • Mirror: left/right axis-mirror symmetry across a vertical line through the map center (players paired up; an odd leftover player is placed on the axis).
  • Resource fields (Tiberium/ore/gem overlays), placed with per-player-wedge balance: patch centers are sampled only within one symmetric "fundamental domain" and then replicated via the same rotation/reflection transform used for player starts, with patch count scaled by playable map area — instead of pure independent random scatter per cell.
  • Deterministic output given the same --seed (fixed a pre-existing bug where TerrainGenerationMutation always reseeded internally from DateTime.Now, which would have silently broken determinism for the new --seed flag; now takes an optional explicit seed, backward-compatible for existing GUI callers).

Terrain algorithm: --algorithm scatter|wfc (default scatter)

  • scatter (default, unchanged from earlier iterations of this PR): runs TerrainGenerationMutation once over the whole map, with all configured tile groups competing independently per cell.
  • wfc (new, opt-in, highest-risk piece — see table above): a real Wave Function Collapse solver (WaveFunctionCollapseSolver.cs) — a from-scratch implementation of the "simple tiled model" variant of the algorithm popularized by mxgmn/WaveFunctionCollapse (MIT licensed; no code from that repo is reused here, only the published algorithm description: observe the lowest-Shannon-entropy cell, collapse it to a weighted-random single state, propagate the resulting adjacency constraints via BFS, detect contradictions). It divides the map into 6x6-cell blocks and solves for which single TerrainGeneratorTileGroup governs each block, using the theater's real LATGrounds ground/base adjacency data (plus tileset index 0, the theater's default base/clear tileset) to build the compatibility matrix — so neighboring blocks only ever get tile groups the game's own tileset rules say are actually allowed to be adjacent. Each block is then painted with the existing TerrainGenerationMutation engine restricted to just that tile group, and the existing auto-LAT blending (already enabled on HeadlessMutationTarget) smooths the boundaries between adjacent blocks, the same way manual GUI tile painting does. Falls back to scatter automatically if the solver hits a contradiction, if a block's generation throws, or if the active preset has fewer than 2 tile groups.

Finalized CLI contract

WorldAlteringEditor.exe --generate-map
    --game-directory <path>     (required; game install dir containing Rules.ini / mix files)
    --theater <name>            (required; e.g. Temperate, Urban, Snow, Desert, Lunar)
    --width <n>                 (required, > 0)
    --height <n>                (required, > 0)
    --players <n>                (required, 2-8)
    --seed <n>                   (optional; omit for a random seed)
    --gamemode <name>           (optional; accepted/logged only, see below)
    --symmetry <rotational|mirror>  (optional; default "rotational")
    --algorithm <scatter|wfc>   (optional; default "scatter")
    --output <absolute path>   (required; .map file to write)

Both space-separated (--flag value) and --flag=value forms are accepted for every flag, to match what the calling client (xna-cncnet-client's ExternalProcessRandomMapGenerator) already expects.

  • Success: exit code 0, stdout: Map generation succeeded. Output written to: <path>
  • Failure: exit code 1, stderr with an error message (missing/invalid args, missing theater preset, I/O failure, etc.)
  • The window is never shown/activated in this mode.

Known open design questions (flagged, not blocking)

  • --gamemode is currently accepted and logged only — it does not yet select a specific TerrainGeneratorPresets.ini section or bias resource density/ratio. Open question for whoever wires up game-mode-specific behavior.
  • Ore vs. gem ratio is not controlled — all OverlayType.Tiberium == true types (ore and gem pooled together) are picked uniformly at random per patch.
  • Mirror-mode resource balance is only left/right (2 replicas), not fully per-player, for player counts > 2 — see inline comment in PlaceResources / plan notes for details. Rotational mode doesn't have this limitation.
  • Resource density constant (~900 cells per patch attempt per symmetric replica) and WFC block size (6x6 cells) are first-pass constants, not tuned against real game balance/visual-quality data.

Files changed

  • src/TSMapEditor/Initialization/HeadlessMapGenerator.cs (new) — HeadlessRmgOptions (CLI parsing/validation), HeadlessMapGenerator (orchestration: terrain, resources, player starts, save), SymmetryMode and TerrainAlgorithm enums.
  • src/TSMapEditor/Initialization/WaveFunctionCollapseSolver.cs (new) — the generic WFC solver used by the wfc terrain algorithm.
  • src/TSMapEditor/Initialization/HeadlessMutationTarget.cs (new) — minimal non-UI IMutationTarget stub.
  • src/TSMapEditor/Program.cs — parses --generate-map and its options.
  • src/TSMapEditor/Rendering/GameClass.cs — hides the window and runs headless generation instead of showing MainMenu when --generate-map is passed.
  • src/TSMapEditor/Mutations/Classes/TerrainGenerationMutation.cs — added optional explicit seed parameter (backward compatible) to fix determinism.

License note

This repo is GPLv3. This PR is a straightforward feature addition to the existing GPLv3 codebase (not a fork), so no additional licensing action is needed beyond normal repo contribution. The new WaveFunctionCollapseSolver.cs reimplements a publicly documented algorithm from scratch; it does not copy code from the MIT-licensed reference implementation, and MIT is compatible with GPLv3 in any case.

BlackgamerzVN and others added 3 commits July 9, 2026 06:56
- New --generate-map CLI flag (Program.cs) parses theater/width/height/players/seed/output args.
- GameClass runs headless generation instead of the interactive UI when requested,
  hiding the window (still needed for GraphicsDevice/TheaterGraphics) and exiting
  with a status code.
- New HeadlessMapGenerator builds a Map via Map.InitNew, loads TheaterGraphics,
  runs the existing TerrainGenerationMutation engine over the whole playable area,
  scatters basic Tiberium/ore resource fields, places N-way symmetric player-start
  waypoints, and writes the result via Map.AutoSave.
- New HeadlessMutationTarget: minimal non-UI IMutationTarget implementation so
  TerrainGenerationMutation can be driven headlessly.
- TerrainGenerationMutation now accepts an optional explicit seed (was previously
  always DateTime.Now-based) so headless generation can be deterministic.

Proof-of-concept scope per plan.md: produces a structurally valid, loadable,
symmetric map with resources and player start points. Not build-verified in this
environment (no network access for NuGet restore + missing sibling INI Parser
checkout); verified via careful manual signature matching against existing call
sites (MapSetup.cs, TerrainGeneratorConfigWindow.cs).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- HeadlessRmgOptions.ParseArgs now accepts space-separated '--flag value' args
  (matching the xna-cncnet-client ExternalProcessRandomMapGenerator contract),
  in addition to the previously-only-supported '--flag=value' form.
- Added optional --gamemode <name> flag (accepted and logged; does not yet
  alter generation - see plan.md open question).
- Logged gamemode alongside seed/output on successful generation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…eadless RMG

- Introduce SymmetryMode enum (Rotational, Mirror) and --symmetry CLI flag.
- Refactor PlacePlayerStarts to support both N-fold rotational symmetry and
  left/right axis-mirror symmetry (with odd-player-count axis placement).
- Rewrite PlaceResources to generate patch centers within a single
  fundamental domain (one rotational wedge, or one mirror half) and
  replicate them via the same symmetry transform used for player starts,
  so resource patches are balanced per-player instead of purely random.
  Patch budget now scales with playable map area.
- Extract shared PlacePatch/GetSymmetryEllipse/ClampToLocalSize helpers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@ZivDero

ZivDero commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

That looks... quite plain. As far as the maps it can make go

@Metadorius

Copy link
Copy Markdown
Member

bru

@BlackgamerzVN

Copy link
Copy Markdown
Author

fuckkk I forgot to fork it

@Metadorius

Copy link
Copy Markdown
Member
image that looks like a fork?

@ZivDero

ZivDero commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

image that looks like a fork?

I feel like something went wrong in launching AI to work on it -> the "could not build" thing. That would indicate the PR was made automatically, too, though xD

@BlackgamerzVN

Copy link
Copy Markdown
Author

It is, and AI fucker sent it directly to HERE xD

@ZivDero

ZivDero commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

It is, and AI fucker sent it directly to HERE xD

You should probably reconsider giving it such broad permissions

@BlackgamerzVN

Copy link
Copy Markdown
Author

noted.

- New WaveFunctionCollapseSolver: a from-scratch implementation of the
  'simple tiled model' WFC algorithm (observe lowest-entropy cell, collapse,
  propagate adjacency constraints via BFS, detect contradictions). Based on
  the publicly documented algorithm from mxgmn/WaveFunctionCollapse (MIT
  licensed); no code from that repo is reused, only the algorithm description.
- New --algorithm scatter|wfc CLI flag (default: scatter, preserving prior
  behavior exactly). wfc mode divides the map into 6x6-cell blocks and runs
  the solver to decide which single TerrainGeneratorTileGroup governs each
  block, using the theater's real LATGrounds ground/base adjacency data
  (plus tileset index 0, the theater's default base/clear tileset) to build
  the compatibility matrix - so neighboring blocks only ever get tile groups
  the game's own tileset rules say can actually sit next to each other.
- Each block is painted using the existing, already-vetted
  TerrainGenerationMutation engine restricted to just that block's tile
  group; auto-LAT blending (already enabled on HeadlessMutationTarget) then
  smooths the boundaries between adjacent blocks using the same mechanism
  as manual GUI tile painting.
- Falls back to the scatter algorithm if the solver hits a contradiction, if
  a block's generation throws, or if the active preset has fewer than 2 tile
  groups (WFC needs at least 2 domains to be meaningful).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@BlackgamerzVN

Copy link
Copy Markdown
Author

MOTHERFUCKER

@BlackgamerzVN

Copy link
Copy Markdown
Author

Myeah expect future slops

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants