Repository navigation
feat(worldbuilder): Adds an MCP server for AI-driven map, script and skirmish AI editing - #125
Merged
Merged
Conversation
WorldBuilder started with -mcp (or -mcpport:N / WB_MCP_PORT) listens on 127.0.0.1 for line-delimited JSON commands. A worker thread services the socket and hands every command to the UI thread, where it runs through the normal undo system. Commands cover map new/open/save/settings, undo/redo, camera and 3D screenshots, terrain heights, brushes, textures and passability, objects, players and teams, waypoints, trigger and water polygons, and roads. scripts/worldbuilder-mcp contains the Python MCP server (uv project) that exposes these commands as MCP tools, a smoke test, and a README. The repo .mcp.json registers it for Claude Code. Also adds -ignoreAsserts to WorldBuilder so debug-build assertions are logged instead of opening blocking dialogs, which would otherwise stall both startup with mod data and bridge commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
terrain.flood_fill with replace_all, or with a texture that no longer fits in the map, silently did nothing: WorldHeightMapEdit::floodFill opened a confirmation message box, which blocks a bridge command. It now takes an askToConfirm flag; the interactive tool keeps prompting, the bridge skips the prompt and reports clear errors instead. The camera pitch was documented backwards (larger is steeper), and with pitch != 1 the requested x/y was no longer at the screen center, because WorldBuilder tilts the view ray away from the camera target. The bridge now compensates so x/y is the point in the middle of the screen, reports that point back, and rejects pitch <= 0 before changing the camera. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New bridge commands exchange terrain data with image files (north up): - terrain.export_heightmap / import_heightmap: one pixel per vertex, raw heights, 16-bit import, set or add mode with a height range, stretch/exact/center fit, optional smoothing. - terrain.export_mask / import_mask: one pixel per cell for passability, a texture or water coverage; import paints a texture or sets passability where the mask passes a threshold. Images are read with stb_image, compiled only into WorldBuilder. The MCP tools resolve relative paths and can return exported images so the model can inspect the terrain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
map.transform mirrors or rotates the whole map (mirror_x/y, rotate_90/ 180/270, diagonal mirrors). map.symmetrize copies one half, triangle or quarter onto the rest for symmetric multiplayer maps (mirror_x/y, rotate_180, mirror_diag/antidiag, rotate_quarters). Both cover heights, passability, textures, objects, waypoints and areas in one undo step. New WorldHeightMapEdit::copyTransformedFrom re-derives tiles for their new position and re-orients blend layers, including the forced triangle flips of 3-way blends. Cliff UV mapping is reset on copied cells. Symmetrize keeps objects near the axis once, keeps road pairs intact, recreates waypoint links between copies (undoable), and renumbers players in names and owners per copy (Player_1_Start -> Player_2_Start, P1_, Perimeter1, teamplayer0001), with explicit rename pairs and an owner map as overrides. The feature set follows the Genesis map tools by The CWC Team. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New bridge commands, modeled on the Genesis map generator processors: - terrain.generate: layered seeded Perlin noise (fractal, ridged or signed layers) with area limits, feathering and protected spots. - terrain.limit_slope: caps height steps between neighbours; cliff flags follow the new slopes. - terrain.auto_texture: classifies cells as ground, cliff (slope or impassable) or water (height or water polygons), paints base textures with noise overlays, checks texture space up front, then blends. - terrain.blend_all / remove_blends: bulk blending helpers. The noise is ported from Genesis PerlinNoise.cs (Apache-2.0, The CWC Team) with our own seeding; layout and scenery presets come from Genesis and are loaded by the Python server. See scripts/worldbuilder-mcp/NOTICE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds generate, limit_slope, auto_texture and the blend helpers to the smoke test, undoing exactly the steps that changed the map, and documents the procedural terrain tools and the single-client bridge in the README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ai.skirmish_setup creates, for every Player_<N>_Start waypoint, the data the skirmish AI looks up by name: InnerPerimeter<N>/OuterPerimeter<N> areas and three approach paths into the base labeled Center<N>, Flank<N> and Backdoor<N> (the number is the defending player, see doTeamFollowSkirmishApproachPath and buildAIBaseDefenseStructure), plus a CombatZone area. Waypoints stay inside the map and are moved off cliffs and water. Existing data, including unnumbered labels the AI ignores, is replaced, all in one undo step. ai.skirmish_check reports per start which areas and paths exist and lists what is missing. The waypoint links undoable moves to a shared header. The idea follows the SkirmishAIGenerator of Genesis (The CWC Team). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scatters weighted templates over the playable map or a circle/rect by count or density, with random rotation and a deterministic seed. It keeps a minimum spacing, stays clear of roads, of existing objects' footprints, of Player_<N>_Start positions, cliffs and water, and can be limited by slope, texture and named areas. An optional noise mask groups objects into groves. All objects are added in one undo step. The idea follows the TreeGenerator and RockGenerator of Genesis (The CWC Team). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roads.route finds a road between two points (and optional via points) with A* over the heightmap vertices. Cliffs, water, structure footprints and named areas are blocked, slopes add cost and steps above max_grade are forbidden. Stops on blocked ground are reached from the nearest free vertex. The grid path is straightened and nearly collinear points are dropped; a shortcut must be neither clearly costlier nor clearly steeper than the path it replaces, so the road does not cut across features the pathfinder went around. Sharp bends get tight corners, ends snap to existing road points, and the ground under the road can be flattened to a smoothed profile. Road and terrain are one undo step. roads.add now shares its segment builder (mcpBuildRoadChain). The idea follows the RoadGenerator and RoadFlattener of Genesis (The CWC Team). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
map.usage lists the textures (with cell counts), object templates and
road types a map uses and flags templates the game does not know, which
is the starting point for converting a map made for another mod.
map.replace swaps textures, object templates and road or bridge types
everywhere from {from: to} mappings, in one undo step, with a dry run.
Textures keep their blends, objects keep position, rotation, owner and
properties, roads keep their flags. All targets are validated first and
chained mappings are rejected because their result would depend on the
order. The Python tool also reads Genesis-style "from to" list files.
The modify-objects undoable moves to the shared header and is reused by
objects.modify, map.transform and map.replace. The idea follows the
Replace command of the Genesis map tools (The CWC Team).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Places Player_<N>_Start waypoints for 2 to 8 players, evenly spaced around the map center on a rectangle inset from the playable edge. Each base area is flattened with a feathered edge and lifted above water. Existing start waypoints are replaced, and the whole operation is one undo step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grows or crops the map, either to a new size with an anchor or by a number of cells per side. Terrain, textures, passability, objects, waypoints, roads, areas, water and build lists keep their place on the terrain. New cells continue the old edge or get a fixed height and texture. Objects and waypoints that fall off the map can be removed, and areas that cover the whole map are refit. One undo step. Adds WorldHeightMapEdit::resizeBySides for this. Unlike resize() it keeps manual passability, updates the map boundaries and never shows a dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…are found locally Self-contained folders (mods, repacks) carry the original Generals archives in a subfolder that the local scan already loads. The assert for a missing registry install path fired there on every start of the game and of WorldBuilder, before -ignoreAsserts takes effect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lets the MCP bridge do what the script editor and the Teams dialog do, without their windows. Edits work on a copy of the sides list and commit it as one undo step, like the script editor's OK. They are refused while the script editor window is open, since its OK would overwrite them. Scripts: list, get, add, update, delete, move, copy, groups, the catalog of actions and conditions, the values a parameter accepts, search, rename with references, bulk value edits, batches as one undo step, clone a player's set, compare two sets, JSON dump and load, .scb import and export with optional waypoints, areas and units. Teams: typed properties checked against what the game reads, list, get, set, copy, bulk changes, summary, verification. Checks and views: the script editor's reference checks, missing references grouped by cause with suggestions, dead logic, build order, trace of what a team or script sets in motion, scripts and teams per difficulty, object type stats, player editing and build lists. The Python server adds script templates and a Markdown report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exposes data that tools need to describe the game data and that had no public reader: the units and sciences of a production prerequisite, the build time of things and upgrades, the armor sets of a thing, the name, speed and surfaces of a locomotor, the shot delay of a weapon, the name of an armor template and the prerequisites of a science. Read only; no data layout changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lets an AI plan and write the scripts and teams of a faction from what the game data says the faction can do. Bridge: - faction.describe: tech tree and unit stats of a faction from the loaded templates (what builds what, prerequisites, cost, health, armor, weapons, movement, upgrades, sciences, special powers). - app.info: where WorldBuilder runs, so that files can be kept with the game data it loaded. MCP server: - faction_describe: the raw description. - faction_generate / faction_get: a planning description per faction in worldbuilder_factions next to the executable. The generated facts get tiers, build paths, combat numbers and guessed roles; an overrides file holds the hand tuning and is written only once. - faction_check: checks a player's teams and build scripts against the description. - ai_framework_generate / ai_framework_get: finds the skeleton the AI players share by comparing them (identical scripts, scripts differing in parameters, shared flags and timers, team roster and hooks, object lists, names expected of a map). - faction_map / ai_scaffold: derives a new player's AI from an existing one, keeping the shared scripts and translating the rest to the counterparts in the target faction. Adds PyYAML for the overrides files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nces A SideInfo skill set in AIData.ini kept at most 20 sciences; entries after that were dropped without notice in release builds, so the AI never bought them. The limit is now the number of sciences a player can purchase, taken from the slot counts of the science purchase screens. Those counts move from ControlBar.h to a header of their own so that game logic can use them without including the user interface. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build scripts only say what the skirmish AI builds; where it goes comes from the SkirmishBuildList of the side in AIData.ini, and a structure without an entry there is never built. The MCP could not see that file. Bridge: - game.read_file: text of a game data file under Data, read through the engine's file system (folders and archives). - buildlist.set: replaces a player's build list in one undo step. MCP server: - aidata_get: global AI settings and, per side, gatherer counts, base defense structure, science skill sets and the base layout. - buildlist_from_aidata: loads a side's layout into a map player's build list to view and edit. - buildlist_export: writes a build list as a SkirmishBuildList block, optionally into an AIData.ini with a backup. - faction_generate stores the side's AIData with the description; faction_check reports build scripts for structures without a place in the layout, layouts without a command center, entries the faction cannot build, and foreign base defenses and skill set sciences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The option lives in the global data, which is destroyed before the last asserts of a shutdown are raised (the memory leak report among them). Those then showed a dialog although asserts were to be ignored, which also blocks a game that a tool has started. The setting is now remembered once it has been seen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…line Lets tools find out whether AI scripts work by having the game play them: -aiMatch <map> -aiPlayers Faction:difficulty[:team],... starts a skirmish with the local player as observer and no frame rate limit, writes what happens to -aiMatchLog as JSON lines and quits when one side is left or -aiMatchMinutes of game time have passed. -seed makes a match repeatable. The log holds the players, a snapshot every -aiMatchInterval seconds (money, power, rank, score, objects by type, teams), events for teams started, sciences and upgrades gained, the first run of every script and everything the script engine and the AI report, each with the player it belongs to, and at the end what was built and lost by type and how often every script ran. Such a game runs as an instance of its own, so that it can be started while WorldBuilder or another game is open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ai_match_run starts the game's -aiMatch mode and ai_match_report reads its log: per player the build timeline, what the faction never built, teams started, sciences and upgrades bought, scripts and teams that never ran, and what its scripts and AI complained about. - aidata_set_side changes a side's SideInfo in AIData.ini (gatherers, base defense, science skill sets) line by line, so that comments and untouched lines stay as they are. It shows a diff and writes only when given the file to change, keeping a backup. - ai_scaffold proposes the SideInfo of the new side, translated from the source side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An AI match played the installed AI only, so a change could not be tried without overwriting Data\Scripts\SkirmishScripts.scb or AIData.ini. -aiMatchScripts <file> makes the skirmish players take their scripts and teams from that file. -aiMatchAIData <file> is read on top of the game's AIData.ini: its settings, side infos and build lists replace those of the same name. The log's setup record names both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ai_match_run takes scripts="open" to play with the scripts and teams open in WorldBuilder (exported next to the match log), scripts=<path> for any script file, and ai_data=<path> for a changed AIData.ini. With the same seed, a match before and after a change shows what the change did, without installing anything. The report names the files used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An AI match needed a window and took the screen while it ran. With -headless only the game logic is updated, the way replays are simulated: the new game message goes to the logic's command list directly and no window, renderer, audio or input is set up. A match plays out exactly as it does in a window and takes about a third of the time. The loop stops with an error in the log if the game does not get into the match or leaves it by itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ai_match_run starts the game with -headless, so that a match no longer opens a window; headless=false shows it in one, to watch the AI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ted ones An extended object that stated prerequisites got them in addition to those of the object it extends, as the list was only cleared for map overrides. A shipyard extended from another general's shipyard then needed that general's supply center as well as its own, which no player can have; it was only buildable through the TEST_MODE alternative in the data. The first Prerequisites block of an extended object now clears the inherited list, as weapon and armor sets already do. An extended object without such a block keeps the prerequisites it inherits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A map is meant for ships when its map.ini sets EnableShips; scripts test that with SKIRMISH_SHIPS_ENABLED and build shipyards with SKIRMISH_BUILD_SHIPYARD at the map's Shipyard<player>_<n> waypoints. - map.info shows ships_enabled and map.set_info writes EnableShips to the map.ini next to the map, keeping the rest of the file. - ai.skirmish_check reports per player the shipyard waypoints, the naval perimeter and the Naval and NavalFlank paths, and lists what a map with ships lacks. - faction_check knows SKIRMISH_BUILD_SHIPYARD, rejects shipyards built with the wrong action and points out shipyard scripts and teams of ships that do not ask SKIRMISH_SHIPS_ENABLED. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iptions Ships were treated as vehicles: they landed in land slots, had land roles and nothing said what can fight them. - Ships are recognised by KindOf SHIP or SUBMARINE, by moving on water only or by being built in a shipyard, and marked naval, submarine or amphibious. The bridge reports the surfaces of all locomotors of a unit, so that amphibious ones are found. - They get roles and slots of their own (shipyard, naval_battle, naval_anti_air, naval_siege, submarine, naval_transport, naval_support) and are kept out of the land slots. - Damage per second is also given against ship and submarine armor, for every unit. - faction_get has a naval view: shipyard, ships, naval slots, amphibious types, what hurts ships most, what a map needs for ships and notes on the data. Weapons that disable, convert or heal no longer count as damage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md was ignored as user-specific; it now holds the guidance for working in this fork and is tracked. The new section describes how to run the WorldBuilder MCP bridge, the rules that hold for every edit, the tools for map editing and for script and team editing, the order of the tools for writing and testing AI, and where to change the MCP itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Model Context Protocol server that lets an AI assistant (Claude Code, or any MCP client) drive a running WorldBuilder. It covers map editing (terrain, objects, waypoints, roads, water) as well as scripts, teams and the skirmish AI around them. It also adds an engine feature that plays headless AI-vs-AI skirmishes and logs them, so that AI changes can be tested.
The bridge only starts with
-mcp. Without it WorldBuilder behaves as before.What's included
WorldBuilder bridge (
GeneralsMD/Code/Tools/WorldBuilder/src/mcp/)Undoable, so each MCP edit is one undo step.roads.route.ai_skirmish_setup/ai_skirmish_checkcreate and check the perimeters,Center<N>/Flank<N>/Backdoor<N>paths and, on maps withEnableShips, shipyard waypoints,NavalPerimeter<N>and theNaval<N>/NavalFlank<N>paths..scbimport/export, and build list editing.faction.describereads templates, prerequisites, sciences, upgrades, weapons, armor and locomotors, so the server can describe what a faction can build and fight.Python server (
scripts/worldbuilder-mcp/)server.py: the MCP tools on top of the bridge (run withuv, registered in.mcp.json).faction.py: generates a faction description (tech tree, unit roles, naval roles, phases) with a reviewable.overrides.yaml, plusfaction_checkagainst a script player.framework.py: the scripts, flags, teams and hooks every skirmish AI shares.scaffold.py: derives a new faction AI from a related faction's player.aidata.py: readsAIData.iniand editsSkirmishBuildListandSideInfosurgically, keeping comments.match.py: runs AI matches and turns their logs into a report.smoke_test.pyexercises the bridge end to end. README has the full tool reference.Engine
GameLogic/AIMatch.h/.cpp):generalszh.exe -aiMatch <map> -aiPlayers Faction:difficulty[:team],... -aiMatchLog <file> [-aiMatchMinutes] [-aiMatchInterval] [-aiMatchCash] [-aiMatchScripts <scb>] [-aiMatchAIData <ini>] [-seed] [-headless]. Plays an observed skirmish between AI players and writes a JSON-lines log (snapshots, teams, sciences, upgrades, scripts that ran, script messages, defeats). With-headlessit runs without the game client, much faster than real time; the same seed gives the same match.ThingTemplate,ProductionPrerequisite,Science,Upgrade,Armor,Locomotor,WeaponandScoreKeeper, for the tools. No behaviour change.Fixes
fix(ai):MAX_AI_UPGRADESwas 20; it now equalsMAX_PURCHASE_SCIENCES(newCommon/SciencePurchaseSlots.h), so a skill set can list every purchasable science.fix(thing): prerequisites set in anObjectExtendnow replace the inherited ones instead of being added to them. This changes game behaviour for mod data that relied on the old merging.fix(debug):-ignoreAssertsstays in effect during shutdown, and WorldBuilder honours it from GameData parsing on.fix(filesystem): skips the "Go install Generals!" assert when the Generals archives were found in the game folder (mods, repacks).HeightMapScale, and the brush tools no longer truncate heights to a byte.fix(worldbuilder): MCP flood fill replace and camera pitch.Docs
CLAUDE.mdis now tracked (removed from.gitignore) and has a WorldBuilder MCP section.Usage
WorldBuilderZH.exe -mcp -ignoreAsserts.worldbuilderserver from.mcp.jsonstarts throughuv.scripts/worldbuilder-mcp/README.mdfor the tools.Testing
z_generalsandz_worldbuilderwith thew32-modding-zhpreset.scripts/worldbuilder-mcp/smoke_test.pyagainst a running WorldBuilder.🤖 Generated with Claude Code