# AoE2Control (CONTROL) — Complete Command Reference with Examples

Compiled from the official docs (`aoe2control.github.io`, cloned to `aoe2control.github.io/docs/`), covering `commands.md`, `facts.md`, `types.md`, `render-api.md`, and `enums.md`.

> **Ground rules**
> - Lua signatures are **strict**: pass every argument shown, even if C++ has defaults.
> - The game must be running for most functions; exceptions: `GetAssignedPlayerId()`, `AssignAndLoadModule()`.
> - Only the **Commands** section is affected by replay blocking, `Update()`-only warnings, and Sequential Actions.
> - Most unit/building commands **silently filter to objects owned by the assigned player**. `SetCameraPosition` and `SendChatMessage` are not ownership-filtered.
> - Use `GetAssignedPlayer()`, not a "local player" concept. Gaia is typically player `0`; regular players are `1`–`8`.

---

## 1. In-Match Commands (the "orders")

### 1.1 Movement & positioning

| Command | Signature | Returns | Example |
|---|---|---|---|
| `UnitsMove` | `(units, position)` | `boolean` | Move all owned archers to a rally point: |
| | | | ```lua<br>local archers = GetAssignedPlayer():GetObjectsByTypes({UnitObjectType.ARCHER})<br>UnitsMove(archers, Vector3(120, 95, 0))``` |
| `SetCameraPosition` | `(position)` | `nil` | Follow your army / jump to action: |
| | | | ```lua<br>SetCameraPosition(Vector2(140, 60))``` |
| `CalculatePath` | `(startPos, targetPos)` or `(startPos, targetPos, collisionRadius)` | `Vector3[]` | Native pathfinding between two points (empty list = no path): |
| | | | ```lua<br>local path = CalculatePath(Vector3(20, 20, 0), Vector3(60, 60, 0), 0.5)<br>Log("Waypoints: " .. tostring(#path))``` |

### 1.2 Targeting & combat

| Command | Signature | Returns | Example |
|---|---|---|---|
| `UnitsTargetObject` | `(units, target)` | `boolean` | Order units to attack/interact with a specific object: |
| | | | ```lua<br>local enemyTC = GetObjectsByType(UnitObjectType.TOWN_CENTER_FEUDAL_AGE)<br>if #enemyTC > 0 then UnitsTargetObject(myArmy, enemyTC[1]) end``` |
| `SetUnitStanceAttackMove` | `(units, targetPosition)` | `nil` | Classic attack-move (A-move) to a point: |
| | | | ```lua<br>SetUnitStanceAttackMove(knights, Vector3(90, 40, 0))``` |
| `SetUnitCombatStance` | `(units, stance)` | `nil` | Set combat behavior: `UnitCombatStance.AGGRESSIVE` (0), `DEFENSIVE` (1), `NO_ATTACK` (2), `STAND_GROUND` (3): |
| | | | ```lua<br>SetUnitCombatStance(myArmy, UnitCombatStance.DEFENSIVE)``` |

### 1.3 Garrison & stances

| Command | Signature | Returns | Example |
|---|---|---|---|
| `SetUnitStanceGarrison` | `(units, targetObject)` | `nil` | Put units into a building/ship: |
| | | | ```lua<br>local dock = GetObjectsByType(UnitObjectType.DOCK)<br>if #dock > 0 then SetUnitStanceGarrison(galleys, dock[1]) end``` |
| `SetUnitStanceUngarrison` | `(sourceObjects, unit)` | `nil` | Remove one unit from owned buildings: |
| | | | ```lua<br>local docks = GetAssignedPlayer():GetTownCenters() -- any garrisonable list<br>SetUnitStanceUngarrison(docks, galley)``` |
| `SetUnitStanceGuard` | `(units, targetObject)` | `nil` | Guard a specific object (TC, resource): |
| | | | ```lua<br>SetUnitStanceGuard(guardians, goldMine[1])``` |
| `SetUnitStanceFollow` | `(units, targetObject)` | `nil` | Follow an object around: |
| | | | ```lua<br>SetUnitStanceFollow(couriers, GetAssignedPlayer():GetTownCenters()[1])``` |
| `SetUnitStancePatrol` | `(units, targetPosition)` | `nil` | Patrol toward a point: |
| | | | ```lua<br>SetUnitStancePatrol(patrolCavalry, Vector3(75, 30, 0))``` |
| `SetUnitStanceSeekShelter` | `(units)` | `nil` | Send damaged units to auto-shelter: |
| | | | ```lua<br>SetUnitStanceSeekShelter(wounded)``` |
| `SetUnitStanceAutoScout` | `(units)` | `nil` | Auto-scout stance on chosen units: |
| | | | ```lua<br>local scouts = GetAssignedPlayer():GetObjectsByTypes({UnitObjectType.SCOUT_CAVALRY})<br>SetUnitStanceAutoScout(scouts)``` |

### 1.4 Production, research, economy

| Command | Signature | Returns | Example |
|---|---|---|---|
| `TrainUnit` | `(unitId)` / `(unitId, amount)` / `(trainSources, unitId)` / `(trainSources, unitId, amount)` | `boolean` | Train one villager (auto-picks a matching source), or 5 archers from specific buildings: |
| | | | ```lua<br>TrainUnit(UnitObjectType.VILLAGER_MALE)<br>TrainUnit({UnitObjectType.BARRACKS_CASTLE_AGE}, UnitObjectType.ARCHER, 5)``` |
| `ResearchTechnology` | `(technology)` | `boolean` | Research auto-resolves the right building: |
| | | | ```lua<br>if CanResearch(Technology.LOOM) then ResearchTechnology(Technology.LOOM) end``` |
| `UnitsBuildStructure` | `(builders, structureId, position)` | `boolean` | Order villagers to place a structure at a world point: |
| | | | ```lua<br>local builders = GetAssignedPlayer():GetObjectsByClass(UnitClass.VILLAGER)<br>UnitsBuildStructure(builders, UnitObjectType.BARRACKS_DARK_AGE, Vector3(52, 48, 0))``` |
| `SetGatherPoint` | `(buildings, targetPosition)` | `nil` | Redirect a building's gather point: |
| | | | ```lua<br>local mills = GetAssignedPlayer():GetObjectsByTypes({UnitObjectType.MILL})<br>SetGatherPoint(mills, Vector3(48, 51, 0))``` |
| `RingTownBell` | `(building, isCallingIn)` | `nil` | Call villagers in (attack) or send them out: |
| | | | ```lua<br>local tcs = GetAssignedPlayer():GetTownCenters()<br>RingTownBell(tcs[1], true)  -- attack!``` |
| `SendBackToWork` | `(building)` | `nil` | Return workers from one building to previous jobs: |
| | | | ```lua<br>SendBackToWork(barracks)``` |
| `SendAllBackToWork` | `(building)` | `nil` | Same, for all workers of that building: |
| | | | ```lua<br>SendAllBackToWork(tcs[1])``` |

### 1.5 Scouting

| Command | Signature | Returns | Example |
|---|---|---|---|
| `EnableScouting` | `()` | `boolean` | Turn on auto-scout for an idle scout-line unit: |
| | | | ```lua<br>if EnableScouting() then Log("Auto-scout active") end``` |

### 1.6 Destruction

| Command | Signature | Returns | Example |
|---|---|---|---|
| `DeleteUnit` | `(unit)` | `nil` | Delete an owned unit (e.g. a stray): |
| | | | ```lua<br>DeleteUnit(strayVillager)``` |
| `DestroyBuilding` | `(building)` | `nil` | Demolish an owned building: |
| | | | ```lua<br>DestroyBuilding(oldBarracks)``` |

### 1.7 Communication

| Command | Signature | Returns | Example |
|---|---|---|---|
| `SendChatMessage` | `(message)` | `nil` | Chat as the assigned player (not ownership-filtered): |
| | | | ```lua<br>SendChatMessage("Bot: castle age reached")``` |

### 1.8 Engine / session control (menu-level)

| Function | Signature | Returns | Example |
|---|---|---|---|
| `DispatchStartGame` | `()` | `boolean` | Start the configured session (also restarts from an end screen): |
| | | | ```lua<br>options:SetPlayerCivilization(0, OptionsCivilization.KOREANS)<br>DispatchStartGame()``` |
| `DispatchRestartGame` | `()` | `boolean` | Restart a single-player session: `DispatchRestartGame()` |
| `DispatchResignGame` | `()` | `boolean` | Resign: `DispatchResignGame()` |
| `DispatchQuitGame` | `()` | `boolean` | Exit toward menus: `DispatchQuitGame()` |
| `DispatchLoadGame` | `(saveGameFileName)` | `boolean` | Load a save by exact file name from the load list: |
| | | | ```lua<br>for _, f in ipairs(GetAvailableSaveFiles()) do<br>  if f:sub(-13) == ".aoe2record" then DispatchLoadGame(f); break end<br>end``` |
| `GetAvailableSaveFiles` | `()` | `string[]` | List loadable saves (exact names, not paths). |
| `GetCurrentGameOptions` | `()` | `GameOptions \| nil` | Inspect/modify pending session setup; guard against `nil`. |
| `IsGamePaused` | `()` | `boolean` | Check pause state. |
| `IsMenuOpen` | `()` | `boolean` | Check menu state (works in replays too). |

### 1.9 Replay / speed control

| Function | Signature | Returns | Example |
|---|---|---|---|
| `SetGameSpeedMultiplier` | `(multiplier)` | `nil` | Turbo mode: `SetGameSpeedMultiplier(5.0)` |
| `SetGamePaused` | `(paused)` | `nil` | Pause/resume game or replay: `SetGamePaused(true)` |
| `SetReplaySpeed` | `(speed)` | `nil` | `SetReplaySpeed(ReplaySpeed.FASTEST)` (SLOW / NORMAL / FAST / FASTEST) |
| `GetCurrentReplayFileName` | `()` | `string` | Name of the replay being watched. |

### 1.10 Engine lifecycle

| Function | Signature | Returns | Example |
|---|---|---|---|
| `Log` | `(message)` | `nil` | Write to the CONTROL log window: `Log("hello")` — usable from any callback. |
| `SetEngineUIVisibility` | `(visible)` | `nil` | Hide overlay for streamers: `SetEngineUIVisibility(false)` |
| `UnloadEngine` | `()` | `nil` | Detach CONTROL from the game process programmatically. |
| `GetAssignedPlayerId` | `()` | `number` | Player id of this module instance; available in `Load`. |
| `AssignAndLoadModule` | `(playerId, moduleName)` | `boolean` | Assign a module to player slot 1–8 and load/reload it: |
| | | | ```lua<br>AssignAndLoadModule(2, "my_bot")``` |

---

## 2. Facts — reading game state (all queries return for the assigned player unless noted)

### 2.1 Core facts & attributes

| Function | Signature | Returns | Example |
|---|---|---|---|
| `GetFact` | `(fact)` / `(fact, parameter)` | `number` | Population: `GetFact(Fact.POPULATION, 0)`; wood: `GetFact(Fact.WOOD_AMOUNT, 0)` |
| `GetAttribute` | `(attribute)` | `number` | Resources/age: `GetAttribute(PlayerAttribute.GOLD)`, `GetAttribute(PlayerAttribute.AGE)` |
| `CanAfford` | `(unitId, isBuilding)` | `boolean` | Cost check (population + resources); does **not** imply availability — pair with `IsObjectTypeAvailable`: |
| | | | ```lua<br>if IsObjectTypeAvailable(UnitObjectType.HOUSE_DARK_AGE) and CanAfford(UnitObjectType.HOUSE_DARK_AGE, true) then<br>  TrainUnit(UnitObjectType.HOUSE_DARK_AGE)<br>end``` |
| `IsObjectTypeAvailable` | `(unitObjectType)` | `boolean` | Do I have access to this unit/building type? |
| `GetUnitTypeCount` | `(unitId)` | `number` | How many of a type do I own: `GetUnitTypeCount(UnitObjectType.VILLAGER_MALE)` |
| `GetTechCost` / `GetObjectCost` | `(technology)` / `(unitObjectType[, multiplier])` | `{resourceId, amount}[]` | Read current costs: |
| | | | ```lua<br>for _, e in ipairs(GetTechCost(Technology.LOOM)) do<br>  if e.resourceId == ResourceType.GOLD then Log("Loom gold: " .. e.amount) end<br>end``` |
| `CanResearch` / `IsTechnologyResearched` | `(technology)` | `boolean` | Gate research. |

### 2.2 Object queries (world scans — not just your objects!)

| Function | Signature | Returns | Example |
|---|---|---|---|
| `GetObjectsByType` | `(unitType)` | `Object[]` | All world objects of one type: `GetObjectsByType(UnitObjectType.GOLD_MINE)` |
| `GetObjectsByTypes` | `(unitTypes)` | `Object[]` | Any of several types at once. |
| `GetObjectsByClass` | `(unitClass)` | `Object[]` | By class (e.g. all villagers): `GetObjectsByClass(UnitClass.VILLAGER)` |
| `GetObjectById` | `(id)` | `Object` | Resolve a stable id back to an object. |
| `GetObjectsInArea` | `(pos1, pos2)` | `Object[]` | Everything inside the rectangle between two `Vector2` points: |
| | | | ```lua<br>local nearby = GetObjectsInArea(Vector2(50, 40), Vector2(70, 60))``` |
| `GetAllProjectiles` / `GetProjectilesByType` | `()` / `(projectileType)` | `Object[]` | Visible projectiles; e.g. `GetProjectilesByType(ProjectileType.TREBUCHET)`. |

> **Fog caveat:** with *Modules See Everything* off, object scans follow the assigned player's fog. On non-visible objects only `IsVisible()`, `IsExplored()`, `GetId()`, `GetPosition()`, `GetClass()`, `GetUnitObjectType()` are safe. Dead huntables/livestock that carry loot are still included.

### 2.3 Players, map, time, chat

| Function | Signature | Returns | Example |
|---|---|---|---|
| `GetAssignedPlayer` | `()` | `Player` | Your player object. |
| `GetPlayerById` | `(id)` | `Player` | Any player (0 = Gaia, 1–8 = players). |
| `GetPlayerCount` | `()` | `number` | Size of the world player list. |
| `IsEnemyPlayer` | `(player)` | `boolean` | Enemy check vs assigned player. |
| `GetGameTime` | `()` | `number` | Match time in seconds. |
| `GetMapWidth` / `GetMapHeight` | `()` | `number` | Map size in tiles. |
| `GetMapTile` | `(x, y)` or `(position)` | `MapTile \| nil` | Tile at integer coords (or a `Vector2`, which is floored). |
| `GetAllMapTiles` | `()` | `MapTile[]` | Full grid; tile methods still respect fog. |
| `GetMapTilesPtr` / `GetObjectsPtr` | `()` | `(ptr, count)` | Packed engine buffers for IPC / `ReadProcessMemory` readers (count = elements, not bytes). |
| `GetObjectTypeData` | `(objectTypeId, objectData)` | `number` | Static type data: train time etc. |
| `GetObjectTypeAttribute` | `(objectTypeId, objectAttribute, damageType)` | `number` | Static type attributes: |
| | | | ```lua<br>local hp = GetObjectTypeAttribute(UnitObjectType.VILLAGER_MALE, ObjectAttribute.HITPOINTS, 0)<br>local t = GetObjectTypeData(UnitObjectType.VILLAGER_MALE, ObjectData.TRAIN_TIME)``` |
| `GetAllChatMessages` / `GetNewChatMessages` / `GetLastChatMessage` | `()` | `string[]` / `string \| nil` | Chat buffer (full / unread-since-last-call / newest). |
| `GetVictoryCondition` / `GetVictoryPlayer` | `()` | `VictoryCondition` / `Player \| nil` | End-state queries. |

---

## 3. Navigation

### 3.1 Camera

- **Set:** `SetCameraPosition(Vector2(x, y))` — world/tile-space center point.
- **Read (global):** `GetCameraPosition()` → `Vector2`; `GetZoom()` → number.
- **Read (per player):** `player:GetCameraPosition()` → `Vector2`.

```lua
function Update()
    -- keep camera glued to my army's centroid
    local army = GetAssignedPlayer():GetObjectsByTypes({UnitObjectType.KNIGHT, UnitObjectType.ARCHER})
    if #army > 0 then
        local cx, cy = 0, 0
        for _, u in ipairs(army) do
            local p = u:GetPosition()
            cx = cx + p.x; cy = cy + p.y
        end
        SetCameraPosition(Vector2(cx / #army, cy / #army))
    end
end
```

### 3.2 Pathfinding

- **Point-to-point:** `CalculatePath(startPos, targetPos[, collisionRadius])` → `Vector3[]` waypoints (native engine pathfinding; empty list = no route).
- **Per-object:** `object:CalculatePath(targetPos)` → uses the object's own game-reported radius.
- **Live path of a moving unit:** `object:GetPath()` → current native path waypoints.

```lua
function Render()
    local target = GetObjectsInArea(Vector2(100, 80), Vector2(104, 84))
    if #target > 0 then
        local path = CalculatePath(Vector3(30, 30, 0), target[1]:GetPosition(), 0.5)
        for i = 1, #path - 1 do
            RenderWorldLine(path[i], path[i + 1], Color(0, 255, 0, 200), 1.5)
        end
    end
end
```

### 3.3 Map tiles (walkability / buildability)

`GetMapTile(x, y)` → `MapTile`:

| Method | Use |
|---|---|
| `GetPosition()` | Tile as `Vector2(x, y)` |
| `GetTerrain()` | `Terrain.GRASS`, `WATER`, `FOREST_PINE`, … (`UNKNOWN` if unexplored) |
| `GetElevation()` | Elevation number (0 if unexplored) |
| `GetTileVisibility()` | `TileVisibility.UNEXPLORED` / `VISIBLE` (15) / `EXPLORED` (128) |
| `IsWalkable()` | Collision-aware walkability (false if unexplored) |
| `IsNavigatable()` | Pathing-usable (false if unexplored) |
| `IsBuildable()` | Flat + walkable enough for foundations (false if unexplored) |
| `GetObjectCount()` / `GetObjects()` | Visible objects on the tile (visible tiles only) |

```lua
function Update()
    local tile = GetMapTile(40, 40)
    if not tile then return end
    if tile:GetTileVisibility() == TileVisibility.VISIBLE then
        Log(string.format("tile %d,%d terrain=%s elev=%s objects=%d",
            40, 40, tostring(tile:GetTerrain()),
            tostring(tile:GetElevation()), tile:GetObjectCount()))
    end
end
```

### 3.4 Coordinate spaces & conversion (Render API)

| Space | Use for |
|---|---|
| **Screen** (`Vector2`) | HUD, labels, fixed UI — `RenderText`, `RenderLine`, `RenderCircle(Filled)`, `RenderRect(Filled)` |
| **World** (`Vector3`) | Unit highlights, placement markers — `RenderWorldLine/Rect/Circle/Text`, `RenderObjectBounds(Filled)` |
| **Minimap** | Strategic dots/lines — `RenderMinimapDot/Line/Rect(Filled)` |

Conversions:

```lua
local screen = WorldToScreen(unit:GetPosition())   -- Vector3 world -> Vector2 screen
local mini   = WorldToMinimap(unit:GetPosition())  -- Vector3 world -> Vector2 minimap
if IsWorldPosOnScreen(unit:GetPosition()) then ... end
if IsOnScreen(screen) then ... end
local size   = GetScreenSize()                      -- Vector2
```

**Full pipeline — mark every enemy TC on the minimap:**

```lua
function Render()
    for i = 0, GetPlayerCount() - 1 do
        local p = GetPlayerById(i)
        if p and IsEnemyPlayer(p) then
            for _, tc in ipairs(p:GetTownCenters()) do
                RenderMinimapDot(tc:GetPosition(), 4.0, Color(255, 64, 64))
                RenderObjectBounds(tc, Color(255, 64, 64, 255), 2.0)
            end
        end
    end
end
```

---

## 4. Unit control (full workflow)

Typical control loop — check affordability, train, redirect:

```lua
function Update()
    local me = GetAssignedPlayer()
    if not me then return end

    -- economy: keep villagers at 20
    if me:GetUnitTypeCount(UnitObjectType.VILLAGER_MALE) < 20
        and IsObjectTypeAvailable(UnitObjectType.VILLAGER_MALE)
        and CanAfford(UnitObjectType.VILLAGER_MALE, false) then
        TrainUnit(UnitObjectType.VILLAGER_MALE)
    end

    -- tech: loom as soon as possible
    if CanResearch(Technology.LOOM) then
        ResearchTechnology(Technology.LOOM)
    end

    -- military: attack-move scouts when 5+ knights exist
    local knights = me:GetObjectsByTypes({UnitObjectType.KNIGHT})
    if #knights >= 5 then
        SetUnitCombatStance(knights, UnitCombatStance.AGGRESSIVE)
        SetUnitStanceAttackMove(knights, Vector3(110, 70, 0))
    end
end
```

Garrison / shelter / delete patterns:

```lua
-- garrison archers into my castle
local castles = me:GetObjectsByTypes({UnitObjectType.CASTLE})
if #castles > 0 then SetUnitStanceGarrison(archers, castles[1]) end

-- wounded units seek shelter
SetUnitStanceSeekShelter(woundedUnits)

-- cleanup: delete a stray, demolish an old building
DeleteUnit(stray); DestroyBuilding(oldBuilding)
```

**Strategic components** (higher-level unit control, init in `Init()`):

- `ResourceTracker` — `GetTrees()`, `GetGold()`, `GetStone()`, `GetFarms()`, `GetForage()`, `GetOwnedLivestock()`, `GetConvertibleLivestock(pos, radius)`.
- `VillagerOccupation(tracker)` — counts by profession, idle villager pool, `RequestVillagers(amount, position, UrgencyLevel.HIGH)`, `AssignVillagers(objects)`, priority tuning (`SetPriorities(wood, food, gold, stone)`).
- `ConstructionPlacement(occupation)` — `BuildStructure(structureType, targetPos, PlacementDirection.East, 1.0)`, `BuildStructureAtTown(structureType)` (resumes unfinished foundations), `FindBestPosition(...)`, `QueueBuildingRequest(...)` + `ProcessBuildingRequests()`.

```lua
local resources, villagers, construction

function Init()
    resources = ResourceTracker()
    villagers = VillagerOccupation(resources)
    construction = ConstructionPlacement(villagers)
end

function Update()
    resources:Update()
    villagers:Update()
    construction:Update()
    if CanAfford(UnitObjectType.BARRACKS_DARK_AGE, true) then
        construction:BuildStructureAtTown(UnitObjectType.BARRACKS_DARK_AGE)
    end
end
```

---

## 5. Target selection

How to pick *what* to target:

| Need | API |
|---|---|
| Everything of one type in the world | `GetObjectsByType(UnitObjectType.X)` |
| Anything from a set of types | `GetObjectsByTypes({UnitObjectType.A, UnitObjectType.B})` |
| By class (villagers, cavalry…) | `GetObjectsByClass(UnitClass.VILLAGER)` |
| Only **my** units | `player:GetObjectsByTypes(...)`, `player:GetObjectsByClass(...)`, `player:GetTownCenters()` |
| Rectangle region | `GetObjectsInArea(Vector2(minX,minY), Vector2(maxX,maxY))` |
| Resolve a stored id later | `GetObjectById(id)` (stable across frames; objects themselves are callback-scoped) |
| What I'm looking at / clicked | `player:GetSelectedObject()`, `player:GetMouseHoveredObject()` |
| What a unit is already targeting | `unit:GetTargetObject()`, `unit:GetTargetPosition()` |
| Visible projectiles (siege impacts) | `GetAllProjectiles()`, `GetProjectilesByType(ProjectileType.TREBUCHET)`; impact point via `proj:GetActionTargetPosition()` |

**Fog-safe targeting pattern:**

```lua
function Update()
    local me = GetAssignedPlayer()
    if not me then return end

    -- find nearest VISIBLE enemy town center
    local enemies = GetObjectsByTypes({
        UnitObjectType.TOWN_CENTER_FEUDAL_AGE,
        UnitObjectType.TOWN_CENTER_CASTLE_AGE,
    })
    local myTC = me:GetTownCenters()[1]
    if not myTC then return end

    local best, bestDist
    for _, e in ipairs(enemies) do
        if e:IsAlive() and e:IsVisible() and IsEnemyPlayer(e:GetOwningPlayer()) then
            local d = myTC:GetPosition():Distance(e:GetPosition())
            if not bestDist or d < bestDist then
                best, bestDist = e, d
            end
        end
    end

    if best then
        UnitsTargetObject(me:GetObjectsByClass(UnitClass.CAVALRY), best)
    end
end
```

**Idle-unit detection** (for scout assignment): `unit:IsIdle()`, plus `IsMoving()`, `IsScouting()`.

---

## 6. Parsing unit / NPC data

### 6.1 Object — instance-level data

| Method | Returns | Notes |
|---|---|---|
| `GetId()` | `number` | **Stable id** — retain this, not the object |
| `GetName()` / `GetInternalName()` | `string` | Display name / engine internal name (`GetMasterName()` is an alias) |
| `GetObjectType()` | `ObjectType` | High-level: unit, building, projectile… |
| `GetUnitObjectType()` | `UnitObjectType` | Exact type id (see community Objects Table for the full list) |
| `GetClass()` | `UnitClass` | Class id (see community ClassId table) |
| `GetOwningPlayer()` | `Player` | Ownership (Gaia = 0 for wild NPCs) |
| `GetPosition()` | `Vector3` | World position |
| `GetCurrentMapTile()` | `MapTile` | Tile it stands on |
| `GetDirection()` | `Vector3` | Facing vector |
| `GetHitpoints()` / `GetMaxHitpoints()` | `number` | Current / max HP |
| `IsAlive()` / `IsIdle()` / `IsMoving()` / `IsScouting()` | `boolean` | State flags |
| `IsVisible()` / `IsExplored()` | `boolean` | Fog state for the assigned player |
| `GetGarrisonObject()` | `Object` | Current garrison container |
| `GetTargetObject()` / `GetTargetPosition()` | `Object` / `Vector3` | What its AI is currently targeting |
| `GetActionTargetPosition()` | `Vector3` | Action target (projectile impact point) |
| `GetPath()` | `Vector3[]` | Current path waypoints |
| `CalculatePath(targetPos)` | `Vector3[]` | Path from this object using its own radius |
| `GetAttribute(attribute, damageType)` | `number` | Instance attribute: `ObjectAttribute.HITPOINTS` (0), `LINE_OF_SIGHT` (1), `SPEED` (5), `ARMOR` (8), `WEAPON` (9), `WEAPON_RANGE` (12) |
| `GetObjectData(objectData)` | `number` | Instance data field (e.g. `ObjectData.POINT_X`) |

### 6.2 Static type data (before units exist / for cost math)

```lua
local hp   = GetObjectTypeAttribute(UnitObjectType.VILLAGER_MALE, ObjectAttribute.HITPOINTS, 0)
local time = GetObjectTypeData(UnitObjectType.VILLAGER_MALE, ObjectData.TRAIN_TIME)
Log("Villager HP=" .. hp .. ", train time=" .. time)
```

### 6.3 NPC (Gaia) parsing

Wild animals and neutral resources belong to Gaia (`GetPlayerById(0)`):

```lua
function Update()
    local gaia = GetPlayerById(0)
    if not gaia then return end

    for _, o in ipairs(gaia:GetPlayerObjects()) do
        -- deer, boar, sheep…
        Log(string.format("NPC: %s id=%d at (%.1f, %.1f) hp=%d",
            o:GetName(), o:GetId(),
            o:GetPosition().x, o:GetPosition().y, o:GetHitpoints()))
    end
end
```

Or scan by class across the whole world: `GetObjectsByClass(UnitClass.LIVESTOCK)` etc.

### 6.4 Player — aggregate data

| Method | Returns |
|---|---|
| `GetId()`, `GetPlayerName()`, `GetPlayerType()` (`HUMAN`/`BOT`/`GAIA`) | identity |
| `GetCivilizationId()` / `GetCivilizationName()` | civ |
| `GetColor()` | `Color` (UI color) |
| `HasWon()` | boolean (finished games) |
| `IsAlliedWith(player)` / `IsEnemyTo(player)` | relations |
| `GetPlayerObjects()` | all owned objects |
| `GetTownCenters()` | owned TCs |
| `GetUnitTypeCount(id)` | per-type count |
| `GetFact(fact, parameter)`, `GetAttribute(attribute)` | resources/pop/age (fog-limited for other players) |
| `IsObjectTypeAvailable(t)`, `CanAfford(id, isBuilding)` | availability / cost |
| `GetResearchState(tech)` (`LOCKED`…`RESEARCHED`), `CanResearch(tech)`, `IsTechnologyResearched(tech)`, `CanAffordResearch(tech)` | tech state |
| `GetTechCost(tech)`, `GetObjectCost(type[, mult])` | cost entries `{resourceId, amount}` |
| `GetCameraPosition()`, `GetSelectedObject()`, `GetMouseHoveredObject()` | view state |

### 6.5 Retention rule (important!)

`Object`, `Player`, `MapTile` userdata are **callback-scoped views**. Don't hold them across callbacks — keep the **id or position**, re-query each frame:

```lua
local trackedIds = {}          -- stable
function Update()
    local alive = {}
    for _, id in ipairs(trackedIds) do
        local o = GetObjectById(id)   -- re-resolve every frame
        if o and o:IsAlive() then alive[#alive + 1] = id end
    end
    trackedIds = alive
end
```

Commands re-resolve ids against the current world generation and reject stale targets.

---

## 7. Coordinate locations — quick reference

| Want | How |
|---|---|
| Object's position in world | `obj:GetPosition()` → `Vector3(x, y, z)` (z = elevation) |
| Object's tile | `math.floor(pos.x), math.floor(pos.y)` or `obj:GetCurrentMapTile():GetPosition()` → `Vector2` |
| World → screen | `WorldToScreen(worldPos)` |
| World → minimap | `WorldToMinimap(worldPos)` |
| Camera center | `GetCameraPosition()` (global) / `player:GetCameraPosition()` |
| Map bounds in tiles | `GetMapWidth()`, `GetMapHeight()` |
| Distance between two points | `Vector3:Distance(other)` or `Vector2:Distance(other)` |
| Path waypoints | `CalculatePath(a, b[, radius])` / `obj:GetPath()` |
| Build/move targets | Always pass **world** positions: moves/builds take `Vector3`, camera takes `Vector2` (x,y) |

Vector math available on `Vector2/3/4`: `+ - * /`, unary `-`, `==`, `Length()`, `Normalize()`, `Dot`, `Cross`, `Lerp(target, alpha)`, `IsNearlyZero()`.

```lua
local pos = unit:GetPosition()
local tileX, tileY = math.floor(pos.x), math.floor(pos.y)
local distToTC = (pos - tcPos):Length()
if IsWorldPosOnScreen(pos) then
    local s = WorldToScreen(pos)
    RenderText(string.format("%s (%d,%d)", unit:GetName(), tileX, tileY), s, 14.0, Color(255,255,255), true, true)
end
```

---

## 8. Module skeleton (where all of this runs)

```lua
-- modules/my_bot/my_bot.main.lua
local me

function Load(playerId)
    Log("Loaded for player " .. tostring(GetAssignedPlayerId()))
end

function Init()
    me = GetAssignedPlayer()
end

function Update()
    -- economy / scouting / combat decisions here (commands allowed)
end

function Render()
    -- overlays only; no game commands
end

function End(hasWon)
    Log("Game over, won=" .. tostring(hasWon))
    DispatchRestartGame()
end
```

Config: `%appdata%\CONTROL\AoE2Control\`; module file `modules/{name}/{name}.main.lua`. Headless mode (`--headless`) for script-driven pipelines; IPC via named pipe `AoE_ML_Pipe` (JSON) + packed snapshots (`GetObjectsPtr`, `GetMapTilesPtr`) for external agents.
