Skip to content

Expose a bot/agent API for Zero Hour, in the spirit of BWAPI #3328

Description

@sebyx07

Idea: a bot/agent API for Zero Hour, in the spirit of BWAPI

The short version

StarCraft: Brood War has had BWAPI since 2009 — a C++ framework that lets external programs observe game state and issue unit commands. It turned a 1998 game into the standard testbed for RTS AI research, and it is still generating activity in 2026: AIIDE and IEEE CoG competitions, the SSCAIT ladder, dozens of open-source bots, and a steady stream of academic papers. None of that was planned by the game's developers; it happened because somebody exposed a stable API.

Generals: Zero Hour is now in a better position than Brood War ever was — the actual source is here, it builds, it's GPLv3, and it's actively maintained. BWAPI had to do all of this by injecting into a closed binary and reverse-engineering memory offsets. We wouldn't have to.

I'd like to gauge whether there's appetite for a first-class agent API, and if so, agree on a shape before anyone writes code.

(Filing here rather than in Discussions > Ideas because it's as much an architecture question as a feature request — happy for a maintainer to move it if Ideas is the better home.)

Why this repo is unusually well suited to it

The hard part of a bot API is usually determinism and a headless mode. This project already has both, for its own reasons:

  • Core/GameEngine/Source/Common/ReplaySimulation.cpp — ReplaySimulation::simulateReplays() already runs replays without graphics, across worker processes, and returns non-zero on mismatch.
  • .github/workflows/reusable-check-replays.yml already runs that in CI against a corpus of replays.
  • The command stream is already serialized and validated for network play and replays (GameMessageParser, NetCommandMsg, NetCommandList).

So the expensive infrastructure — a deterministic, graphics-free simulation you can run many of in parallel — exists. A bot API is substantially "expose what's already there", not "build a new engine mode".

Proposed shape

The design goal is that a bot is indistinguishable from a human player at the engine boundary. That's what protects everything this project cares about.

1. Commands go through the existing GameMessage path.
Bot orders would be enqueued as the same GameMessages that mouse and keyboard input produce, dispatched via GameLogicDispatch. Consequences that matter here:

  • Replays record bot games for free, in the existing format.
  • Network determinism and CRC checking are unaffected — the engine cannot tell the difference, so there is no new mismatch surface.
  • A bot cannot do anything a player couldn't do, which sidesteps a whole class of design arguments.

2. Observation is a read-only view, fog-respecting by default.
A versioned, read-only snapshot of what the controlled player can legitimately see: objects, positions, health, states, resources, upgrades, supply, map/terrain, visibility. BWAPI's equivalent lesson is worth copying exactly — its CompleteMapInformation flag is off by default, so bots see only what a player sees, and a tournament module can pin it. Same for a UserInput equivalent. Get the default right on day one and the "is this bot cheating" argument never has to happen.

3. Out-of-process bridge, not just an in-process DLL.
BWAPI started in-process only and later added a shared-memory client bridge, because in-process has two chronic problems: a crashing or hanging bot takes the game with it, and everyone is locked to one language and ABI. Starting with a bridge means bots can be written in Python, Rust, Java or anything else, and a misbehaving bot is a dead pipe rather than a crash report filed against this repo. Notably, the one BW bot that currently uses a neural network (Pluto) runs its inference engine as a separate 64-bit process talking to a 32-bit DLL over stdio pipes — precisely because in-process wasn't an option.

4. A headless + uncapped-speed mode for training and tournaments.
Mostly a matter of reusing the ReplaySimulation path with a live agent driving instead of a recorded command stream.

Suggested staging

Deliberately incremental, because per CONTRIBUTING.md this shouldn't land as one enormous pull request:

Stage Scope Gameplay risk
1 Read-only observation API + headless observation of existing replays None — no new commands exist
2 Command emission via GameMessage, skirmish only, behind a build flag and an off-by-default command-line switch Low — no new code path in the engine
3 Out-of-process bridge + a reference bot and one non-C++ binding None to the game
4 Tournament/ladder harness, match runner, result reporting None to the game

Stage 1 alone is already independently useful: a stable read-only observation API over replays gives the project replay analytics, balance telemetry and regression tooling, whether or not anyone ever writes a bot.

Open questions for maintainers

  1. Is this something the project wants at all, or is it out of scope? Worth knowing before any design work.
  2. Should it live in-tree, or as a separate repo under the org consuming a small set of hooks added here? I lean toward the second — it keeps this repo's diff tiny and lets the API iterate at its own pace.
  3. Zero Hour first, per the precedence rule in CONTRIBUTING.md, with Generals to follow — is that right here, or is this one where sharing via Core/ from the start makes more sense?
  4. Is there any appetite for the observation API to also serve the existing skirmish AIPlayer / AISkirmishPlayer, or should those stay entirely separate?
  5. Anything in the 1.04 compatibility rules that a bot API would trip over that isn't obvious from outside?

Offer to help

I'm willing to do the implementation work, starting with the smallest useful slice (Stage 1) so there's something concrete to react to rather than a wall of text.

On tooling, to be straightforward about it up front since AI_POLICY.md asks: I use AI-assisted development in my workflow. I've read the policy and I'm not proposing to point an agent at the codebase and open pull requests — that's explicitly out of bounds and I agree with the reasoning. Anything I submit would be disclosed per section 2, scoped small enough to actually review, and I'd be accountable for explaining every line of it in review, in my own words. If the project would rather this area stay entirely hand-written, say so and I'll respect that.

Happy to take this to Discord instead if that's a better venue for hashing out the design.

Activity

  1. bobtista commented on Sep 21, 2026

    @bobtista

    I like the idea, and keeping the existing command machinery and keeping bot implementations outside the engine sound like the right moves. Which frame boundary does the bot observe? When do its actions execute, and what happens if it stalls? We'd have to build out a bunch of headless stuff that is currently skipped/dummy, eg ghost-objects, all of the game ending stuff. We'd also need to check that all getters only get and don't change state or we'll end up with mismatches. And it's one thing to have the local human player played by a bot - but to have more than one bot on a machine would need us to change a bunch of stuff. And some things in code are possible that aren't possible in the UI, like MSG_DO_ATTACK_OBJECT on a shrouded thing - people could abuse this, so we'd want to find these spots in the code and harden them.
    I still think something like this is going to get put on the back burner. Maybe start with an experimental replay-observation interface and a minimal external client, prove that one live bot-controlled match can be recorded and replayed correctly? Your best bet imho is to keep TSH changes minimal and make a working demo that gets people excited about it.

  2. added
    AIIs AI related
    EnhancementIs new feature or request
    ZHRelates to Zero Hour
    on Sep 21, 2026
  3. added theissue type on Sep 21, 2026
  4. xezon commented on Sep 21, 2026

    @xezon
    • Is this something the project wants at all, or is it out of scope? Worth knowing before any design work.

    It sounds useful long term.

    • Should it live in-tree, or as a separate repo under the org consuming a small set of hooks added here? I lean toward the second — it keeps this repo's diff tiny and lets the API iterate at its own pace.

    The piping makes sense. Keep simple communications to the game client, and the bot itself lives in a separate process making its calls to the game client. Yes the bot machinery can be a separate repository then, targeted for any programming language. The game client just gives the contact for interacting with it.

    • Zero Hour first, per the precedence rule in CONTRIBUTING.md, with Generals to follow — is that right here, or is this one where sharing via Core/ from the start makes more sense?

    New files go into Core. Code that touches files not yet merged into Core goes to GeneralsMD first, and then is replicated to Generals before its merged into main branch.

    • Is there any appetite for the observation API to also serve the existing skirmish AIPlayer / AISkirmishPlayer, or should those stay entirely separate?

    I think the classic scripted AI players are kept seperate from the new machine learned AI players. They are controlled by different mechanisms.

    • Anything in the 1.04 compatibility rules that a bot API would trip over that isn't obvious from outside?

    If the bot can only post GameMessages and observe game states, then it should not change game logic. I think there needs to be some though put into how a bot can observe the world and how that is implemented nicely across many classes. He should not see whats in the shroud (and maybe also not what is not in camera view, bot needs to scroll like a human?), but he should see everything a human player would see and use that data for decision making.

  5. sebyx07 commented on Sep 21, 2026

    @sebyx07
    Author

    AI disclosure: this comment was drafted with Claude (Claude Code) and posted at my direction; I stand behind it and will answer follow-ups myself.

    Thanks @xezon and @bobtista, that feedback was very useful. Following it, I've written a spec only, no code so we can agree on the design before anything is implemented: #3338.

    docs/plans/2026/09/21/101-bot-agent-api/overview.md has a table that maps each of your points to where it's answered. Short version:

    • Frame semantics / stalls: the bot observes the end of frame N. Its orders execute at N+1 in skirmish and N+run-ahead in network games, exactly like a human's. In realtime mode the game never waits for the bot; step mode (the game waits) only runs in skirmish/headless.
    • Getters that mutate: the spec lists the ones found so far (Object::getShroudedStatus writes partition/ghost caches, WeaponTemplate::getDelayBetweenShots advances the logic RNG, createGroup bumps a CRC'd counter). It proposes a pure snapshot writer, plus a replay test with a CRC every frame to prove purity.
    • Shrouded targets: confirmed that onDoAttackObject only checks the target exists. The spec proposes UI-parity checks in the bridge before a message is created (no CRC impact), and treats logic-side hardening as separate optional PRs behind RETAIL_COMPATIBLE_CRC.
    • More than one bot per machine: one bot per game process. Two people each run their own bot on their own machine in an ordinary LAN game, and nothing in the engine needs to support multiple local players.
    • Separate repo + Core: the engine side is a small, off-by-default bridge in Core/; SDKs, Gym env, MCP server and referee live elsewhere.
    • Visibility: fog-respecting by default, a camera mode as you suggested, and full only for replays/analysis — refused in network games. Bot-controlled players get a [BOT] tag in their name.

    Two things I learned while researching:

    • NewShoes (https://github.com/Agusx1211/NewShoes, see its issue Game can crash when clicking on player icon in Replay Mode #75) already has a working, fog-respecting agent bridge for the Zero Hour WebAssembly port. I'd like to align with it rather than invent a second, incompatible protocol.
    • A correction to my original post: Pluto is not the only neural-network Brood War bot, and its 64-bit process is its own sidecar, not BWAPI's client bridge. It did win CoG 2026 (4–1 over PurpleWave in the final). The widely reported wins over pro players came from an unauthorized copy on the Remastered ladder that reportedly used a maphack — a good argument for enforcing fog on the engine side.

    Also, in case it's useful: at developerz-ai, where I work, we've distilled about two years of building software with AI agents into https://github.com/developerz-ai/gold-standards-in-ai. It goes beyond the plan layout used in the PR: how to organize a repo so AI coding agents work well in it (CLAUDE.md, guard rails and lint checks, fast feedback loops, planning conventions, MCP design). No expectation to adopt any of it, but some ideas might fit this repo.

  6. xezon commented on Sep 22, 2026

    @xezon
    1. Where do orders enter? Inject into TheCommandList right after propagateMessages() (GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp:910) — bypassing UI translators — or into TheMessageStream so translators see them? Plan assumes the former.

    The regular path (TheMessageStream) is safest, because then a human can observe the bot in a client. Headless could go through TheCommandList, but is not strictly needed if TheMessageStream also works for it.

    1. Observation hook: end of GameLogic::update just before m_frame++ (GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp:3935) so it also fires in headless replay simulation, which never calls GameEngine::update. OK?

    Yes at the end of a logic update it makes sense.

    1. Object visibility: Object::getShroudedStatus mutates PartitionData caches. Reimplement its rules as a pure read, or accept calling it from logic in fixed order? Plan prefers pure.

    Yes all bot observations of game state must not mutate game state.

    1. UI-parity hardening in logic (reject attacks on shrouded targets etc.) — separate PRs behind RETAIL_COMPATIBLE_CRC, or bridge-side only until retail compatibility can be broken?

    We can harden this yes so that the bot really cannot do illegal attacks. Must be behind RETAIL_COMPATIBLE_CRC and keep the checks simple at the injection points if possible.

    Other than that, game state observation should hide targets that are not visible, so the bot should not be able to react to it, unless it tries to cheat on its own (by remembering old target ids and figuring that it can still target them when assumed hidden).

    1. Camera-bound mode as xezon suggested — required, optional, or later?

    Optional. It naturally nerfs the effectiveness of the bot.

    1. Wire format: length-prefixed JSON first (C++98/VC6-friendly, no new deps) with a binary encoding later — acceptable?

    Json is fine.

    1. Separate repo name/ownership under the org, or a personal repo until it proves itself?

    You can create the Bot stuff on your own repository until it has matured.

    1. Build gating: CMake option (off by default) + command-line flag, or command-line flag only?

    Cmake option. It must be possible to compile out for builds that aim at competitive play and do not want to be cheated. What is the command line for?

    1. Disclosure: [BOT] suffix in the player name (visible to retail clients and in replays) — OK, or another form?

    Yes ideally bots are marked as such.

    1. One agent, several slots: is a skirmish/headless-only multi-seat mode (one process, orders stamped per seat) acceptable later, or should it always be one process per slot? See 05 §One agent, several player slots.

    I don't know. What speaks for and what against the options?

    1. Plan location: keep docs/plans/… in-tree, move to the wiki, or keep only in the external repo?

    Plans are your own. You can link them in this topic.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    AIIs AI relatedEnhancementIs new feature or requestZHRelates to Zero Hour

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions