# AoE2Control (CONTROL) — Analysis & Feature Compilation

**Source:** https://aoe2control.github.io/ → repo `AoE2Control/aoe2control.github.io` (MkDocs docs site), cloned at `Workspace/aoe2control.github.io/` (branch `main`, commit `5180d7f`).
**Engine releases:** https://github.com/aoe2control/AoE2Control/releases (extraction password: `control`)
**Date of analysis:** 2026-09-04

---

## 1. What It Is

AoE2Control ("CONTROL") is a **Lua scripting engine for Age of Empires II: Definitive Edition** (Windows, 64-bit). A launcher EXE attaches to the already-running game process and injects a Lua VM plus an in-game ImGui overlay. Modules written in Lua can:

- **Read live game state** — units, buildings, resources, players, map tiles, fog-aware object visibility, projectiles, victory state
- **Send commands** — train units, move armies, research techs, garrison, build structures, send chat
- **Render overlays** — HUD text/shapes in screen space, world space, or minimap space
- **Automate the session** — configure match setup (map, civ, age, victory rules), start/restart/resign/quit, load saves
- **Talk to external processes** — Windows named pipes + JSON (IPC) for ML/AI agents, with raw-memory snapshot buffers (`ReadProcessMemory`-friendly packed structs)

Key design point: it talks **directly to game state**, not pixel scanning or mouse/keyboard simulation. That is exactly what makes it a powerful cheat tool and a clean automation platform at the same time.

---

## 2. How It Is Used for Cheating in AoE2

CONTROL itself is neutral infrastructure; "cheating" comes from combining its features against the game's information and control model. The cheat surface, feature by feature:

### 2.1 Information cheats (seeing what you shouldn't)

| Feature | Cheat effect |
|---|---|
| **Modules See Everything** (MODULES submenu, default Off) | Lua modules ignore fog-of-war and cross-player data restrictions entirely: map tiles, objects, and player state (resources, facts, techs) of all players become readable. Full-map radar for a bot or an overlay. |
| **Player Perspective** override (MISC submenu) | Forces the *rendered* fog view to any player's perspective (`Player 1`–`8`, `Gaia`) — human-visible X-ray vision, not just Lua-readable. Docs warn "may cause crashes." |
| **Spectator Mode** (MISC) | Spectator-style reveal behavior with controls still available — see the whole map while playing. |
| **Facts / Player API** (`GetFact`, `GetAttribute`, `Player:GetFact/GetResearchState/...`) | Exact numbers: food/wood/gold/stone amounts, population cap/headcount, per-player tech states, unit counts — no estimation from HUD. |
| **`GetAllMapTiles()` / `GetMapTile(x,y)`** | Full terrain/elevation/walkability grid (fog-aware unless "See Everything"), enabling perfect base placement and scouting logic. |
| **`GetObjectsByType/Class`, `GetObjectsInArea`** | Enumerate every world object (including explored animals/resources outside vision), garrisons, projectiles — complete battlefield awareness. |

### 2.2 Control cheats (acting without the human)

| Feature | Cheat effect |
|---|---|
| **Suppress Native AI** (default On) | Assigning a module to a bot slot disables that player's built-in AI — the Lua module becomes the brain of that slot, in single- or multiplayer. |
| **Per-player module slots (Player 1–8)** | Run different modules per player in one session: e.g., you get an info overlay while your "ally" is a full auto-economy bot. |
| **Command API** (`TrainUnit`, `UnitsMove`, `UnitsTargetObject`, `ResearchTechnology`, stances, garrison, town bell, ...) | Direct command dispatch bypassing selection and input — perfect timing at any game speed. |
| **Game Speed Multiplier / Turbo Mode** (MISC + `SetGameSpeedMultiplier`) | Run the match faster than the UI normally allows (e.g., hold a key to 3×) — compress long games, react instantly in slow moments. |
| **Unlock Zoom** (MISC) | Removes the normal zoom limit for tactical overview. |
| **`SetCameraPosition` / Auto Move Camera** | Camera teleports programmatically; combined with auto-move it always centers on your actions. |

### 2.3 Setup cheats (rigging the match before it starts)

`GetCurrentGameOptions()` exposes the full setup screen as writable Lua:

- **Full Tech Tree** (`SetFullTechTree`) — every technology available from the start
- **Reveal Map** (`SetRevealMap`) — fog permanently lifted
- **Resource presets** (`SetResources`), **population cap**, **starting/ending age** (e.g., start in Castle Age)
- **AI difficulty** preset, **handicap** per player slot, **treaty length**, **victory rules/limits**
- **Map selection**: any fixed map or a custom random-map pool (`SetRandomMapPoolLocations`)
- **Civ assignment per slot**, team locks, speed lock, recording toggle

Then `DispatchStartGame()` kicks off the match — the whole setup→start pipeline is scriptable (used heavily by tournament/ML harnesses).

### 2.4 Fairness guardrails (what keeps it from being *only* a cheat)

- **Tournament Mode** — blocks game commands outside `Update()`, restricts engine/menu/replay/render/`GameOptions` APIs, and penalizes updates over 20 ms by adding delay to the effective update interval. Designed so modules behave like real players under a common rule set.
- **Fog-aware defaults** — with "Modules See Everything" off, Lua only sees what the assigned player's fog allows (small exception: explored animals/resources). `IsVisible()`/`IsExplored()` gate safe reads.
- **Sequential Actions** (default On) — at most one successful command per update tick, preventing godlike micro spam.
- **Ownership filtering** — most unit/building commands silently filter to objects owned by the assigned player.
- **Sandboxed Lua** — `load`, `loadfile`, `dofile`, `module`, `collectgarbage` removed; `require()` depth ≤ 3; no directory traversal.

### 2.5 Practical "cheat stack" (typical usage)

1. Start AoE2DE, launch CONTROL, press START (or headless: `AoE2Control.exe --headless`).
2. In the overlay: enable **Modules See Everything** + **Player Perspective = Gaia/your player** for X-ray vision; optionally **Spectator Mode**, **Unlock Zoom**, **Game Speed Multiplier**.
3. Assign a module to your slot (and/or bot slots with **Suppress Native AI** on).
4. The module's `Update()` reads facts/objects and issues commands each tick — auto-economy, auto-attack-move, perfect scouting — while `Render()` paints the hidden information as an overlay HUD.
5. For multiplayer: enable cheats in the lobby first (multiplayer is allowed "when cheats are enabled").

**Caveats:** engine may be flagged by antivirus (process injection + memory access); major AoE2DE patches can require new releases; `Player Perspective`/`Unlock Zoom` are explicitly marked as crash-prone.

---

## 3. How To Integrate It Into The Game

### 3.1 Installation & attachment

1. Install **Age of Empires II: Definitive Edition** (Windows 64-bit).
2. Download the latest release from GitHub Releases; extract with password `control`.
3. **Start AoE2DE first**, then run the **CONTROL Launcher** (`AoE2Control.exe`) and press **START**.
4. The engine attaches to the game process, the overlay appears (toggle: `Shift`; eject/detach: `Delete`), and the config folder is created:

```text
%appdata%\CONTROL\AoE2Control\
├── settings.ini     # submenu settings, module instance configs, sync profiles, module settings
├── imgui.ini        # saved overlay layout
└── modules/         # Lua / encrypted module projects
```

### 3.2 Headless integration (script-driven startup)

Start the game first, then:

```text
AoE2Control.exe --headless [--override-settings <file>] [--override-module <file-or-folder>]
```

- `--override-settings` replaces `%appdata%\CONTROL\AoE2Control\settings.ini` before startup.
- `--override-module` copies a `.main.lua` file or whole module folder into `modules/` before startup (existing target replaced).
- Writes one status line per change to stdout: version → `Scanning...` → `Checking game startup...` → `Waiting for connection...` → `Initializing...` → `Fetching Offsets...` → terminal status.
- Terminal success lines: `Ready`, `Ready - Diagnostics pending`, `Ready - Partially outdated`, `Ready - Requires update`, `Already Running!`.
- Failure lines: `Process not found`, `Startup validation failed`, `Offset Error`, `Render Hook Failed`, override copy errors.
- Exit codes: `0` success (incl. already running), `1` another launcher instance, `2` bad args, `3` override failed, `4` startup failed.

This makes CONTROL easy to embed in CI/ML pipelines (docs include a Python `subprocess.Popen` harness that streams status lines and checks the exit code).

### 3.3 Writing & attaching a module

Folder convention: `modules/{moduleName}/{moduleName}.main.lua` (or `.main.module` for encrypted). Discovery is recursive, depth ≤ 3, matching `*.main.lua` / `*.main.module`.

Starter module — all six callbacks are optional:

```lua
function Load(playerId)          -- instance loaded/activated; use Settings.Add* here
    Settings.AddBool("Enabled", true)
    Log("Loading for player " .. tostring(playerId))
end

function Init()                   -- match ready / world changed / reloaded
    Log("Match ready for player " .. tostring(GetAssignedPlayerId()))
end

function Update() end             -- every configured update interval while match runs (commands go here)
function Render() end             -- every rendered frame (overlays), unless Multithreading
function End(hasWon) end          -- once after match/replay ends; manual exit => hasWon=false
function Unload() end             -- instance unloaded/disabled/replaced/engine detached
```

Attach: open overlay → expand **Player N** section → pick module in the **Module** dropdown → keep **Enabled** on → start a single-player match, or a multiplayer match with cheats enabled. Re-selecting the same module reloads it. `AssignAndLoadModule(playerId, moduleName)` does the same programmatically (player ids 1–8).

### 3.4 Module system details

- `require("a.b.c")` → `a/b/c.lua` relative to project root; dots become path separators.
- **Depth limit 3** (`require("a.b.c.d")` fails); per-path caching (second require returns cache).
- No `..` directory traversal; sandbox removes `load`, `loadfile`, `dofile`, `module`, `collectgarbage`.
- **Encrypted modules**: compile the project with the CONTROL module compiler, then request encryption/packaging via Discord; ships as `.main.module` for code protection.

### 3.5 Engine-level options (MODULES submenu)

| Setting | Default | Effect |
|---|---|---|
| Enabled | On | Master toggle for all module instances |
| Update Interval | 1.0 s | Seconds between `Update()` calls (UI clamp 0.1 s; ini clamp 0.01 s) |
| Sequential Actions | On | One successful command per update tick |
| Auto Move Camera | On | Camera centers on executed command targets |
| Command Visualization | On | Draws feedback overlays for commands |
| Suppress Native AI | On | Module-attached bot slots lose built-in AI |
| Tournament Mode | Off | Fair-play restrictions (see §2.4) |
| Multithreading | Off | Module runs off the render thread; `Render()` disabled; some menu/replay/IPC-wait APIs blocked |
| Modules See Everything | Off | Fog-of-war / cross-player read bypass |

Other submenus: **UI** (Menu Scale, Menu Transparency), **MISC** (Player Perspective, Game Speed Multiplier, Spectator Mode, Unlock Zoom, Chat Welcome Message), **KEYBINDS** (Menu Toggle `Shift`, Eject `Delete`), **DEBUG** (Module Telemetry — sampled `Update()`/`Render()` cost vs baseline frame; log window toggle).

### 3.6 Per-player slots & settings profiles

- One module slot per player (1–8): **Enabled**, **Sync Settings** (share one named settings profile across multiple slots instead of per-player values), **Module** picker, **Create/Remove Profile**, **Remove Module**.
- Module-created settings panels appear per configured settings group.
- **VS Code plugin**: `aoe2-control-lua` on the Marketplace — IntelliSense, snippets, full API definitions.
- **AI agent reference**: downloadable `CONTROL_LUA_ENGINE_REFERENCE.md` (full API contract) + `CONTROL_LUA_AGENT_INSTRUCTIONS.md` (slim guardrails) to paste into coding agents.

---

## 4. Feature Compilation (complete catalog)

### 4.1 Engine / control functions

| Function | Returns | Description |
|---|---|---|
| `Log(message)` | nil | Write to CONTROL log window |
| `SetEngineUIVisibility(visible)` | nil | Show/hide overlay |
| `UnloadEngine()` | nil | Detach CONTROL from game process |
| `GetAssignedPlayerId()` | number | Assigned player id (available in `Load`) |
| `AssignAndLoadModule(playerId, moduleName)` | boolean | Assign+load/reload module for slot 1–8 |

### 4.2 Session / menu automation

| Function | Returns | Description |
|---|---|---|
| `DispatchStartGame()` | boolean | Start configured session (restarts if on end screen) |
| `DispatchRestartGame()` | boolean | Restart single-player session when supported |
| `DispatchResignGame()` | boolean | Resign current game |
| `DispatchQuitGame()` | boolean | Exit to menus when supported |
| `DispatchLoadGame(saveGameFileName)` | boolean | Load by exact name from load list (closes load screen on success) |
| `GetAvailableSaveFiles()` | string[] | Names in the game's load-game list |
| `GetCurrentGameOptions()` | GameOptions\|nil | Writable session setup object (see §4.13) |
| `IsGamePaused()` | boolean | Current game/replay paused? |
| `IsMenuOpen()` | boolean | Game UI in menu state (works in replays) |

### 4.3 Replay controls

| Function | Description |
|---|---|
| `SetGameSpeedMultiplier(multiplier)` | Set current game/replay speed multiplier |
| `SetGamePaused(paused)` | Pause/resume session |
| `SetReplaySpeed(speed)` | `ReplaySpeed.SLOW/NORMAL/FAST/FASTEST` |
| `GetCurrentReplayFileName()` | Current replay file name while active |

### 4.4 In-match commands (belong in `Update()`; replay-blocked; Sequential-Actions-scoped)

| Function | Description |
|---|---|
| `SetCameraPosition(position: Vector2)` | Move camera to world position (not ownership-filtered) |
| `SendChatMessage(message)` | Chat as assigned player (not ownership-filtered) |
| `TrainUnit(unitId)` / `(unitId, amount)` / `(trainSources, unitId[, amount])` | Train via auto-resolved or explicit production sources |
| `UnitsTargetObject(units, target)` | Attack/interact/target an object |
| `UnitsBuildStructure(builders, structureId, position: Vector3)` | Builders place a structure |
| `UnitsMove(units, position: Vector3)` | Move owned units |
| `EnableScouting()` | Auto-scout on an idle scout-line unit if available |
| `ResearchTechnology(technology)` | Research via auto-resolved source building |
| `DeleteUnit(unit)` / `DestroyBuilding(building)` | Remove owned object (no success flag) |
| `SetGatherPoint(buildings, targetPosition)` | Set building gather point |
| `RingTownBell(building, isCallingIn)` | Recall/send out villagers |
| `SendBackToWork(building)` / `SendAllBackToWork(building)` | Return workers to previous jobs |
| `SetUnitStanceAutoScout(units)` | Auto-scout stance |
| `SetUnitStancePatrol(units, targetPosition)` | Patrol toward position |
| `SetUnitStanceGuard(units, targetObject)` | Guard an object |
| `SetUnitStanceFollow(units, targetObject)` | Follow an object |
| `SetUnitStanceAttackMove(units, targetPosition)` | Attack-move |
| `SetUnitStanceGarrison(units, targetObject)` / `SetUnitStanceUngarrison(sourceObjects, unit)` | Garrison/ungarrison |
| `SetUnitStanceSeekShelter(units)` | Seek shelter |
| `SetUnitCombatStance(units, stance)` | `AGGRESSIVE/DEFENSIVE/NO_ATTACK/STAND_GROUND` |

### 4.5 Facts (read-only game state; fog-aware unless "See Everything")

- **Costs & affordability:** `CanAfford(unitId, isBuilding)`, `GetTechCost(technology)`, `GetObjectCost(type[, multiplier])` → arrays of `{resourceId, amount}`; `IsObjectTypeAvailable(unitObjectType)`; `CanResearch(technology)`; `IsTechnologyResearched(technology)`
- **Counts & attributes:** `GetFact(fact[, parameter])` (e.g. `POPULATION`, `FOOD_AMOUNT`, `WOOD_AMOUNT`, `GOLD_AMOUNT`, `STONE_AMOUNT`, `POPULATION_CAP`, `POPULATION_HEADROOM`), `GetUnitTypeCount(unitId)`, `GetAttribute(PlayerAttribute)`
- **World queries:** `GetObjectsByType(s)/GetObjectsByClass(class)` (world-wide, dead loot-bearing huntables/livestock included), `GetObjectsInArea(pos1, pos2)`, `GetObjectById(id)`, `GetObjectById`/projectiles: `GetProjectileById(id)`, `GetAllProjectiles()`, `GetProjectilesByType(type)`
- **Map:** `GetMapWidth()/GetMapHeight()`, `GetMapTile(x,y)` / `GetMapTile(Vector2)`, `GetAllMapTiles()`, `GetMapTilesPtr()` → `(ptr, count)` packed buffer
- **Pathfinding:** `CalculatePath(startPos: Vector3, targetPos[, collisionRadius])` → native path `Vector3[]` (empty = no path)
- **Players:** `GetAssignedPlayer()`, `GetPlayerById(id)` (Gaia 0, players 1–8), `GetPlayerCount()`, `IsEnemyPlayer(player)`
- **Time & chat:** `GetGameTime()` (seconds), `GetAllChatMessages()`, `GetNewChatMessages()` (per-instance unread tracking), `GetLastChatMessage()`
- **Victory:** `GetVictoryCondition()`, `GetVictoryPlayer()`
- **Type data:** `GetObjectTypeData(type, ObjectData field)`, `GetObjectTypeAttribute(type, attribute, damageType)`
- **Bulk snapshot:** `GetObjectsPtr()` → `(ptr, count)` dead-inclusive packed objects

### 4.6 Render API (from `Render()`; disabled under Multithreading)

**Screen space:** `GetScreenSize()`, `IsOnScreen(pos)`, `RenderText(text, pos, size, color, center, border)`, `RenderLine(from, to, color, thickness)`, `RenderCircle(position, radius, color, thickness, segments)`, `RenderCircleFilled(...)`, `RenderRect(from, to, color, rounding, roundingCornersFlags, thickness)`, `RenderRectFilled(...)`

**World/minimap space:** `IsWorldPosOnScreen(worldPos)`, `WorldToScreen(worldPos)`, `WorldToMinimap(worldPos)`, `GetZoom()`, `GetCameraPosition()`, `RenderWorldLine/Rect/RectFilled/Circle/CircleFilled`, `RenderWorldText(text, worldPos, size, color, center, border)`, `RenderObjectBounds(object, color, thickness)`, `RenderObjectBoundsFilled(object, color)`, `RenderMinimapDot(worldPos, radius, color)`, `RenderMinimapLine(from, to, thickness, color)`, `RenderMinimapRect(...)`, `RenderMinimapRectFilled(...)`

### 4.7 Settings API (module UI controls)

- **Add (in `Load` only):** `Settings.AddBool(key, default)`, `AddInt(key, default, min, max)`, `AddFloat(key, default, min, max)`, `AddDropdown(key, default, options[])`, `AddKeybind(key, defaultVkCode)`, `AddTooltip(key, tooltip)`, `AddColor(key, defaultColor)`
- **Get (any callback):** `Settings.GetBool/GetInt/GetFloat/GetString/GetKeybind/GetColor` — each requires the fallback argument in Lua.
- **Input:** `IsKeyPressed(vkCode)`

### 4.8 IPC API (named pipes + JSON, for ML/external agents)

| Function | Description |
|---|---|
| `IPC.StartServer(pipeName)` | Start/join named pipe server (local-machine clients; default ACL = CONTROL process's Windows user SID) |
| `IPC.StopServer()` | Stop this instance's endpoint |
| `IPC.Send(message)` | Send string/Lua value to all clients (non-strings auto-JSON); wrapped in `{type:"module_message", pipeName, source:{instanceId, assignedPlayerId, moduleName, settingsGroup}, payload}`; returns false if no client |
| `IPC.HasMessages()` / `IPC.GetMessages()` | Poll queued inbound messages (strings) |
| `IPC.WaitForMessage([timeoutMs])` | Block for one message (nil on timeout/stop); blocked under Multithreading |

- **Routing:** inbound JSON may carry `instanceId`/`assignedInstanceId`, `assignedPlayerId`/`playerId`, `moduleName`, `settingsGroup` at root or in a `target` object; only matching instances receive it.
- **JSON helpers:** `ParseJSON(str)`, `ToJSON(obj)`.
- **Snapshot buffers for ML:** `GetMapTilesPtr()` / `GetObjectsPtr()` return engine-owned packed buffers (copy before the callback boundary). Packed layouts (`#pragma pack(1)`):

```cpp
struct Tile   { uint16 x, y; uint8 terrain, elevation, isVisible, flags; }; // 8 bytes
struct Object { uint32 id; uint16 unitObjectType, x, y; uint8 playerId, flags; }; // 12 bytes
// Tile.flags: bit0 walkable, bit1 navigatable | Object.flags: bit0 alive
```

Flow: Lua gets `(ptr, count)` → sends via IPC → external reader (e.g., Python `ctypes`) does `ReadProcessMemory` on the game process and decodes. Docs include full ctypes definitions.

### 4.9 Types & math

- **Vectors:** `Vector2/3/4` — constructors, `.x/.y[/.z/.w]`, `LengthSqr/Length`, `Normalize()/Normalized()`, `Dot`, `Cross` (2D scalar / 3D vector), `IsNearlyZero`, `Lerp(target, alpha)`, `Distance(other)`; operators `+ - * / - ==`.
- **Color:** `Color(r,g,b[,a])`, float variants, `Color(hexString)`, `Color.Parse(value)`, `Color.HSV(h,s,v)`.

### 4.10 Game object types (userdata; callback-scoped — prefer retaining IDs/positions over objects)

**Object:** `GetId`, `GetName`/`GetInternalName`/`GetMasterName`, `GetObjectType`, `GetOwningPlayer`, `GetGarrisonObject`, `GetTargetPosition`/`GetTargetObject`/`GetActionTargetPosition`, `GetDirection`, `IsVisible`, `IsExplored`, `IsAlive`, `GetUnitObjectType`, `GetClass`, `GetAttribute(attribute, damageType)`, `GetObjectData(field)`, `IsIdle`, `IsMoving`, `IsScouting`, `GetHitpoints`/`GetMaxHitpoints`, `GetPosition`, `GetCurrentMapTile`, `GetPath`, `CalculatePath(targetPos)`

**MapTile:** `GetPosition()→Vector2`, `GetTerrain()` (`UNKNOWN` if unexplored), `GetElevation()`, `GetTileVisibility()`, `IsBuildable`, `IsWalkable`, `IsNavigatable`, `GetObjectCount`, `GetObjects` (only for visible tiles)

**Player:** `GetId`, `GetPlayerType`, `GetPlayerObjects`, `GetCameraPosition`, `GetMouseHoveredObject`, `GetSelectedObject(Count)`, `GetPlayerName`, `GetCivilizationId/Name`, `HasWon`, `IsAlliedWith(player)`, `IsEnemyTo(player)`, `GetColor`, `GetAttribute(attribute)`, `GetUnitTypeCount(id)`, `GetFact(fact, parameter)`, `IsObjectTypeAvailable`, `CanAfford(id, isBuilding)`, `GetResearchState(technology)`, `GetTechCost`, `GetObjectCost(type[, multiplier])`, `CanAffordResearch`, `CanResearch`, `IsTechnologyResearched`, `GetObjectsByTypes`, `GetObjectsByMostCommonType(unitTypes)`, `GetObjectsByClass(unitClass)`, `GetObjectsByClassDeadInclusive`, `GetTownCenters`

### 4.11 Strategic components (higher-level helpers)

- **`ResourceTracker()`** — `Update()`, `GetConvertibleLivestock(pos, radius)`, `GetOwnedLivestock()`, `GetDeadLivestock(pos, radius)`, `GetForage()`, `GetFarms()`, `GetTrees()`, `GetGold()`, `GetStone()`
- **`VillagerOccupation(resourceTracker)`** — villager tracking/reassignment: counts by `VillagerProfession`, idle villagers, `RequestVillagers(amount, position, urgency)`, priority weights (`SetPriorities(wood, food, gold, stone)`, per-profession percentage targets, livestock/forage caps, farm distance limits, profession building range), `AssignVillagers(ids|objects)`, `AssignVillager(v)`
- **`ConstructionPlacement(villagerOccupation)`** — placement engine: town-center padding (default 3), `BuildStructure(...)` overloads (explicit or auto-selected builder, direction enum or world position, optional foundation resume), `BuildStructureAtTown(...)`, `FindBestPosition(...)`, `GetValidFarmPlacementTile()`, queued building requests (`QueueBuildingRequest...`, `ProcessBuildingRequests`, `IsStructureTypeQueued`, `IsUnitAssignedToBuilding`)

### 4.12 Enums (full lists in docs/enums.md)

- **Game:** `PlayerType`, `VictoryCondition`, `ResearchState`, `ReplaySpeed`, `ResourceType`, `PlayerAttribute`, `Age`, `ProjectileType` (~60+ values), `Terrain` (~60 values), `TileVisibility`, `ObjectAttribute`, `UnitClass`, `UnitObjectType` (see community objects table at airef.github.io/tables/objects.html), `Technology` (airef techs table), `UnitCombatStance`, `Fact`, `ObjectData`
- **Strategic:** `UrgencyLevel`, `BuildingPosition`, `VillagerProfession`, `PlacementDirection`, `BuildingRequestPriority`
- **Engine:** `Key` (virtual-key codes)

### 4.13 GameOptions (writable pre-game setup; setters return false while in game)

Global: AI difficulty (`EXTREME…EASIEST`), civilization set, game mode, map size, starting/ending age, game speed, reveal map, victory rule + limit, resources preset, population cap, teams-not-together, recording, team positions, **full tech tree**, lock teams, lock speed, turbo mode, antiquity mode, location (map id) + custom random-map pool, player count, treaty length, handicap.
Per-slot (0–7): team, handicap %, color, civilization; plus `SetAssignedPlayerCivilization(civ)` for the module's own slot.

### 4.14 Lifecycle & timing rules

- Order: `Load → Init → Update ⇄ Render → End`; `Unload` can run from `Load` or `Update` (and without `End`).
- Game API before game start logs an error ("move logic to Init/Update/Render/End"); commands belong in `Update()` (warning otherwise; blocked in Tournament Mode).
- `Object`/`Player`/`MapTile` userdata are callback-scoped views — retain IDs, re-query each callback.
- Multithreading: no `Render()`, blocked `IPC.WaitForMessage`, dispatch fns blocked.

### 4.15 Distribution & ecosystem

- GitHub Releases launcher EXE (password `control`); antivirus may flag it (process injection + memory access).
- Discord: support, module sharing, `.module` encryption/packaging requests.
- YouTube channel for updates/walkthroughs; community in early stages.
- VS Code plugin `aoe2-control-lua`; AI-agent reference docs (`CONTROL_LUA_ENGINE_REFERENCE.md`, `CONTROL_LUA_AGENT_INSTRUCTIONS.md`) for coding agents.

### 4.16 Limits & performance notes

- `require()` depth ≤ 3; module discovery depth ≤ 3.
- Multiplayer allowed when cheats enabled.
- Keep `Render()` light (runs every frame); use Debug → Module Telemetry for sampled `Upd`/`Rnd` cost vs baseline frame.
- Tournament Mode: >20 ms updates add delay to the effective interval.
- `ConstructionPlacement` caches tile state internally.

---

## 5. Quick Reference: Integration Checklist

1. AoE2DE running (Win 64-bit) → run launcher → START (or `--headless`).
2. Config at `%appdata%\CONTROL\AoE2Control\`; modules in `modules/{name}/{name}.main.lua`.
3. Declare settings in `Load`, init state/components in `Init`, issue commands in `Update`, draw in `Render`, clean up in `End`/`Unload`.
4. For ML: `IPC.StartServer("AoE_ML_Pipe")` in `Init`, poll `IPC.GetMessages()` in `Update`, stream `GetObjectsPtr()`/`GetMapTilesPtr()` snapshots; external process connects via `\\.\pipe\AoE_ML_Pipe` (same Windows user).
5. For fairness: Tournament Mode on, Sequential Actions on, fog-aware reads (`IsVisible()` before touching object fields).

## 6. Source Map (cloned repo)

| Doc | Covers |
|---|---|
| `docs/index.md` | Overview, feature table, links |
| `docs/getting-started.md`, `config-location.md`, `distribution.md` | Install, config layout, releases/antivirus |
| `docs/module-system.md`, `lifecycle.md`, `limits.md` | Module format, callbacks, sandbox, limits |
| `docs/interface-options.md` | Overlay submenus incl. cheat-relevant toggles |
| `docs/commands.md`, `facts.md`, `types.md`, `enums.md` | Full Lua API reference |
| `docs/render-api.md`, `render-api-impl.md` | Overlay drawing + examples |
| `docs/settings-api.md` | Module settings UI |
| `docs/ipc-api.md` | Named pipes, JSON routing, snapshot buffers, Python examples |
| `docs/session-control.md`, `game-options.md` | Session automation, writable setup |
| `docs/headless-mode.md` | CLI launcher, exit codes, Python harness |
| `docs/ai-agent-reference.md`, `community.md` | Agent reference downloads, Discord/YouTube/VS Code |
