[
 {
  "name": "status",
  "category": "meta",
  "status": "verified",
  "mechanism": "Блок мира + быстрая полоса + таблица захватов",
  "latency": "Локальный расчёт",
  "signature": "status() -> 'dict'",
  "doc": "Состояние подключения: pid, публикация мира (период, время сбора), счётчики быстрой полосы."
 },
 {
  "name": "snapshot",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок мира W3P Local\\War3World_<pid> (рантайм публикует каждые 50 ms, seqlock)",
  "latency": "Push-снимок",
  "signature": "snapshot(max_age: 'float' = 0.05)",
  "doc": "Полное состояние всей карты (WorldState): .units .players .items .clock .me; повторные вызовы в пределах max_age секунд возвращают одну и ту же копию.\n⚠ Рабочих внутри золотого рудника в списке нет; по умолчанию видна вся карта (в модели lockstep локально есть всё), фильтрация по зоне обзора — только при Game(fair=True)."
 },
 {
  "name": "last_seen",
  "category": "observe",
  "status": "verified",
  "mechanism": "visibleTo из push-снимка (при каждом обновлении снимка запоминаются видимые юниты противника и крипы)",
  "latency": "Push-снимок",
  "signature": "last_seen(owner: 'str | int' = 'enemy', max_age: 'float | None' = None) -> 'list'",
  "doc": "Последние замеченные юниты противника (или крипов — 'creep', или игрока с заданным номером): [(как юнит выглядел тогда, игровые часы в тот момент, сколько секунд прошло)], новые первыми.\nЕсли видели, как юнит погиб, он удаляется из списка. И в честном, и в обычном режиме запись ведётся по принципу «что мы видим прямо сейчас» — это та самая карта в голове игрока:\nразведанные силы, где последний раз видели вражеского героя, когда противник занял экспансию. max_age — только записи не старше указанного числа игровых секунд."
 },
 {
  "name": "map",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок карты W3P Local\\War3Map_<pid> (рантайм вычисляет порциями после начала партии; IsTerrainPathable для прохода/постройки)",
  "latency": "Push-снимок",
  "signature": "map()",
  "doc": "Таблица рельефа этой партии MapInfo: .walkable(x,y) .buildable(x,y) .at(x,y) .bounds (игровая область) .starts (стартовые позиции) .cells (bit0 — непроходимо, bit1 — нельзя строить).\nПосле начала партии вычисляется несколько секунд; пока не готово, возвращает None. Деревьев здесь нет (используйте trees())."
 },
 {
  "name": "me",
  "category": "observe",
  "status": "verified",
  "mechanism": "Заголовок блока мира",
  "latency": "Push-снимок",
  "signature": "me() -> 'int | None'",
  "doc": "Номер нашего игрока (0~11)."
 },
 {
  "name": "resources",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок мира players[16]",
  "latency": "Push-снимок",
  "signature": "resources(player: 'int | None' = None) -> 'dict | None'",
  "doc": "{'gold','lumber','food_used','food_cap','gold_gathered','lumber_gathered'}; player по умолчанию — мы, читать можно любого игрока.\nЕсли прочитать не удалось, возвращает None — не принимайте это за 0."
 },
 {
  "name": "players",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок мира players[16]",
  "latency": "Push-снимок",
  "signature": "players() -> 'list'",
  "doc": "Все 16 слотов игроков: Player(id, gold, lumber, food_used, food_cap, gold_gathered, lumber_gathered, race, known)."
 },
 {
  "name": "units",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок мира units[]",
  "latency": "Push-снимок",
  "signature": "units(owner: 'str | int' = 'all', types=None, alive: 'bool' = True) -> 'list'",
  "doc": "Фильтр юнитов по владельцу/типу. owner: 'me' / 'enemy' / 'creep' / 'all' / номер игрока. types: множество четырёхсимвольных кодов."
 },
 {
  "name": "unit",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок мира by_handle",
  "latency": "Push-снимок",
  "signature": "unit(handle) -> 'object | None'",
  "doc": "Найти юнит по паре дескрипторов (lo, hi) (цель приказа, текущая цель и события отдают именно пары дескрипторов)."
 },
 {
  "name": "is_building",
  "category": "observe",
  "status": "verified",
  "mechanism": "Снимок + units.json (spd==0 = здание)",
  "latency": "Push-снимок",
  "signature": "is_building(u) -> 'bool'",
  "doc": "Является ли юнит зданием (включая башни). Определяется по нулевой скорости передвижения в таблице юнитов; у главного здания Нежити площадь застройки равна 0, так что по площади не определяйте."
 },
 {
  "name": "my_workers",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок",
  "latency": "Push-снимок",
  "signature": "my_workers() -> 'list'",
  "doc": "Наши рабочие (крестьяне/батраки/послушники/огоньки)."
 },
 {
  "name": "idle_workers",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок (слот приказа + слот задания)",
  "latency": "Push-снимок",
  "signature": "idle_workers() -> 'list'",
  "doc": "Рабочие без дела: нет ни приказа, ни задания (те, кому вы дали работу в этом тике, не считаются).\n⚠ Повторный приказ добычи рабочему, у которого есть задание, прерывает цикл добычи (доход падает до нуля)."
 },
 {
  "name": "my_heroes",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок",
  "latency": "Push-снимок",
  "signature": "my_heroes() -> 'list'",
  "doc": "Наши живые герои (погибшие — в списке воскрешения алтаря, см. revive)."
 },
 {
  "name": "my_army",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок + units.json",
  "latency": "Push-снимок",
  "signature": "my_army() -> 'list'",
  "doc": "Наши боевые юниты: не рабочие и не здания."
 },
 {
  "name": "my_buildings",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок",
  "latency": "Push-снимок",
  "signature": "my_buildings(types=None) -> 'list'",
  "doc": "Наши здания (включая башни и строящиеся фундаменты); через types можно запросить только некоторые виды, например {'hbar'}."
 },
 {
  "name": "is_constructing",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок (приказ = четырёхсимвольный код здания либо приказ строительства/ремонта)",
  "latency": "Push-снимок",
  "signature": "is_constructing(worker) -> 'bool'",
  "doc": "Строит ли этот рабочий здание прямо сейчас (или идёт строить / помогает чинить; включая назначенных в этом тике). Выбирая строителя, пропускайте таких, иначе предыдущий фундамент встанет."
 },
 {
  "name": "under_construction",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок (здоровье фундамента растёт от очень малого до полного)",
  "latency": "Push-снимок",
  "signature": "under_construction(building) -> 'bool'",
  "doc": "Здание ещё не достроено (здоровье не полное). ⚠ У повреждённого здания здоровье тоже не полное — в начале партии этого хватает, а после начала боёв учитывайте ещё и время."
 },
 {
  "name": "gold_mines",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок (ngol/egol/ugol)",
  "latency": "Push-снимок",
  "signature": "gold_mines() -> 'list'",
  "doc": "Золотые рудники на карте. ⚠ У Entangled Gold Mine Ночных эльфов и у нейтрального рудника в одной и той же точке по отдельному юниту — на добычу отправляйте к своему."
 },
 {
  "name": "enemies",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок",
  "latency": "Push-снимок",
  "signature": "enemies(fighters_only: 'bool' = False) -> 'list'",
  "doc": "Юниты вражеских игроков (без крипов). fighters_only: без рабочих и зданий."
 },
 {
  "name": "creeps",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок (owner 12 = нейтрально-враждебные)",
  "latency": "Push-снимок",
  "signature": "creeps() -> 'list'",
  "doc": "Крипы (нейтрально-враждебные). ⚠ Ночью обзор короче: когда дальний лагерь уходит в туман войны, команды на цель по нему отклоняются (код причины 1001)."
 },
 {
  "name": "nearest",
  "category": "meta",
  "status": "verified",
  "mechanism": "Чистое вычисление",
  "latency": "Локальный расчёт",
  "signature": "nearest(candidates, to)",
  "doc": "Ближайший к to (юнит или (x,y)); если кандидатов нет — None."
 },
 {
  "name": "life_mana",
  "category": "observe",
  "status": "verified",
  "mechanism": "Юнит блока мира: hp/hpMax/mana/manaMax",
  "latency": "Push-снимок",
  "signature": "life_mana(u) -> 'dict | None'",
  "doc": "{'hp','hp_max','mana','mana_max'} (float, исходные значения движка). В u можно передать юнит прямо из снимка (он будет заменён на самую свежую копию)."
 },
 {
  "name": "hero_info",
  "category": "observe",
  "status": "verified",
  "mechanism": "Юнит блока мира: level/xp/skillPoints",
  "latency": "Push-снимок",
  "signature": "hero_info(hero) -> 'dict | None'",
  "doc": "{'level','xp','skill_points'}."
 },
 {
  "name": "abilities",
  "category": "observe",
  "status": "verified",
  "mechanism": "Детали блока мира: способности (код/уровень/флаги/оставшаяся перезарядка)",
  "latency": "Push-снимок",
  "signature": "abilities(u) -> 'list'",
  "doc": "[{code, level, cooldown, flags}]; баффы — в buffs(u). Есть только у юнитов «с деталями» (герои > юниты игроков > крипы, не больше 256)."
 },
 {
  "name": "buffs",
  "category": "observe",
  "status": "verified",
  "mechanism": "Детали блока мира: объекты способностей, начинающиеся на B",
  "latency": "Push-снимок",
  "signature": "buffs(u) -> 'list'",
  "doc": "Коды баффов на юните (например, 'BHds' — Божественный щит, 'Bslo' — Замедление). Какому эффекту соответствует код, см. data/game/buffs.json."
 },
 {
  "name": "cooldown",
  "category": "observe",
  "status": "verified",
  "mechanism": "Детали блока мира: оставшаяся перезарядка способности (таймер способности)",
  "latency": "Push-снимок",
  "signature": "cooldown(u, ability: 'str') -> 'float | None'",
  "doc": "Сколько ещё секунд (игровых) перезаряжается способность; 0 = можно применять; если такой способности нет (или у юнита нет деталей), возвращает None."
 },
 {
  "name": "inventory",
  "category": "observe",
  "status": "verified",
  "mechanism": "Детали блока мира: 6 ячеек инвентаря",
  "latency": "Push-снимок",
  "signature": "inventory(hero) -> 'list | None'",
  "doc": "Четырёхсимвольные коды предметов в 6 ячейках (пустая ячейка — None); если инвентаря нет, возвращает None."
 },
 {
  "name": "current_order",
  "category": "observe",
  "status": "verified",
  "mechanism": "Юнит блока мира: order / цель приказа / точка цели приказа",
  "latency": "Push-снимок",
  "signature": "current_order(u) -> 'dict | None'",
  "doc": "{'order','target','x','y'}: приказ, который юнит выполняет сейчас (order — 0x000D00xx или четырёхсимвольный код здания, 0 = простой).\ntarget — пара дескрипторов; превратить её в юнит можно через g.unit(target)."
 },
 {
  "name": "current_target",
  "category": "observe",
  "status": "verified",
  "mechanism": "Юнит блока мира: текущая цель (задание)",
  "latency": "Push-снимок",
  "signature": "current_target(u)",
  "doc": "Юнит, которого этот юнит **реально атакует/преследует** (если такого нет — None).\n⚠ После приказа атаки слот приказа быстро пустеет, а атака висит на задании — чтобы понять, «кого бьёт» юнит, используйте это, а не current_order."
 },
 {
  "name": "clock",
  "category": "observe",
  "status": "verified",
  "mechanism": "Заголовок блока мира clockMs (игровые часы движка)",
  "latency": "Push-снимок",
  "signature": "clock() -> 'float | None'",
  "doc": "Игровые часы движка (игровые секунды, во время загрузки 0). На повышенной скорости игры идут быстрее реального времени."
 },
 {
  "name": "production",
  "category": "observe",
  "status": "verified",
  "mechanism": "Таблица производства блока мира (объекты способностей Aque/ABnP/AUnP + прошедшее время, которое отслеживает рантайм; измеренная погрешность < 0.2 игровой секунды)",
  "latency": "Push-снимок",
  "signature": "production(building)",
  "doc": "Что сейчас производит здание: Production(kind, queue, duration, elapsed, blocked, progress, remaining…); если ничего — None.\n  kind 'queue' (найм/исследование/герой, в queue до 7 ячеек, [0] — то, что делается сейчас) / 'construction' (строится) / 'upgrade' (улучшение ратуши/башни);\n  blocked = в очереди есть, но не началось (чаще всего не хватает пищи — пора строить ферму); progress 0..1.\nЗдания противника тоже видны (в честном режиме — только видимые здания)."
 },
 {
  "name": "queue",
  "category": "observe",
  "status": "verified",
  "mechanism": "Таблица производства блока мира",
  "latency": "Push-снимок",
  "signature": "queue(building) -> 'list'",
  "doc": "Четырёхсимвольные коды в очереди найма/исследований ([0] — делается сейчас); если здание простаивает или не производит ничего — []."
 },
 {
  "name": "all_production",
  "category": "observe",
  "status": "verified",
  "mechanism": "Таблица производства блока мира",
  "latency": "Push-снимок",
  "signature": "all_production(owner: 'str | int' = 'me') -> 'list'",
  "doc": "Всё текущее производство [(здание, Production)]. owner — как в units(): 'me' / 'enemy' / номер игрока / 'all'.\nКак используют профи: смотреть, каких юнитов нанимает противник, что исследует и когда улучшает ратушу (если его здания разведаны)."
 },
 {
  "name": "path_distance",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок карты (IsTerrainPathable движка) + блок деревьев + площадь зданий, A* на стороне SDK (клетка 128)",
  "latency": "Push-снимок + локальный расчёт",
  "signature": "path_distance(a, b) -> 'float | None'",
  "doc": "Длина пути наземного юнита от a до b (a и b — юниты или (x,y)); если не дойти — None. На островных картах, чтобы понять, «можно ли дойти по земле до этого лагеря крипов/экспансии», используйте это —\nнадёжнее расстояния по прямой (обходит лес, обрывы, здания). Точность — одна клетка 128; проход уже одной клетки считается непроходимым."
 },
 {
  "name": "reachable",
  "category": "observe",
  "status": "verified",
  "mechanism": "То же, что выше",
  "latency": "Push-снимок + локальный расчёт",
  "signature": "reachable(a, b) -> 'bool | None'",
  "doc": "Можно ли дойти по земле (блок карты ещё не вычислен = None)."
 },
 {
  "name": "walk_path",
  "category": "observe",
  "status": "verified",
  "mechanism": "То же, что выше",
  "latency": "Push-снимок + локальный расчёт",
  "signature": "walk_path(a, b) -> 'list | None'",
  "doc": "Точки поворота пути [(x,y)...] (последняя — b); вместе с path(units, список_точек) ведёт отряд этим маршрутом (в обход башен, обходными тропами)."
 },
 {
  "name": "upkeep",
  "category": "observe",
  "status": "inferred",
  "mechanism": "Фиксированное правило 1.27: 0~50 пищи — без налога, 51~80 — доход ×0.7, 81~100 — ×0.4",
  "latency": "Push-снимок",
  "signature": "upkeep(player: 'int | None' = None) -> 'dict | None'",
  "doc": "Уровень содержания: {'level': 'none'/'low'/'high', 'income': 1.0/0.7/0.4, 'next_at': пища, с которой начинается следующий уровень (нет = None)}.\nАзбука профи: пока улучшаете ратушу до третьего уровня / исследуете улучшения атаки/брони, держитесь на 50 пищи, а до 80 поднимайтесь только перед решающим боем."
 },
 {
  "name": "xp_to_next",
  "category": "observe",
  "status": "verified",
  "mechanism": "level/xp блока мира + формула NeedHeroXP из MiscGame",
  "latency": "Push-снимок",
  "signature": "xp_to_next(hero) -> 'int | None'",
  "doc": "Сколько опыта не хватает герою до следующего уровня (на 10 уровне = 0)."
 },
 {
  "name": "creep_camps",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-снимок (крипы объединяются в группу в радиусе 600) + уровни из units.json",
  "latency": "Push-снимок",
  "signature": "creep_camps(link: 'float' = 600.0) -> 'list'",
  "doc": "Группирует (видимых) крипов на поле в лагеря: [{'x','y','units','level','hp','max_level'}], от ближних к нашей главной базе к дальним.\nlevel = суммарный уровень лагеря (обычная мера сложности крипинга), hp = суммарное здоровье. Выбирайте лагерь вместе с time_to_kill / path_distance."
 },
 {
  "name": "buff_info",
  "category": "observe",
  "status": "verified",
  "mechanism": "data/game/buffs.json (BuffID из AbilityData.slk -> способность/эффект/длительность)",
  "latency": "Локальные данные",
  "signature": "buff_info(code: 'str') -> 'dict | None'",
  "doc": "Что означает код баффа: {'ability','effect','dur','hero_dur','targets'} (например, 'Bslo' -> Замедление). Если коду соответствует несколько строк, возвращается первая."
 },
 {
  "name": "stats",
  "category": "observe",
  "status": "verified",
  "mechanism": "Таблицы данных (UnitBalance/UnitWeapons/UpgradeData/MiscGame) + текущие уровни технологий + уровень героя",
  "latency": "Push-снимок + быстрая полоса (пачка раз в 5 секунд)",
  "signature": "stats(u, player: 'int | None' = None)",
  "doc": "Боевые характеристики юнита combat.UnitStats: максимум здоровья/маны, броня (с учётом улучшений атаки/брони и ловкости героя), тип брони, скорость передвижения, дневной/ночной обзор,\nоружие (по каким целям бьёт, дальность, интервал атаки, диапазон урона, тип атаки, урон по площади). u — юнит (автоматически берутся технологии его владельца и уровень героя) или четырёхсимвольный код (player по умолчанию — мы).\nДальше — .dps_vs(противник) / .hits_to_kill(противник) / combat.time_to_kill(группа, противник). ⚠ Предметы, ауры и баффы не учитываются."
 },
 {
  "name": "time_to_kill",
  "category": "observe",
  "status": "verified",
  "mechanism": "stats() + текущее здоровье",
  "latency": "Push-снимок",
  "signature": "time_to_kill(attackers, target) -> 'float | None'",
  "doc": "За сколько игровых секунд эта группа юнитов вместе убьёт target (по текущему здоровью target; учитываются контры, броня, улучшения атаки/брони; не учитываются перемещение, урон по площади, лечение).\nКак используют профи: фокус огня сначала на того, кто «умрёт быстрее всех» (минимальный time_to_kill), а не на ближайшего. Не могут атаковать = None."
 },
 {
  "name": "time_of_day",
  "category": "observe",
  "status": "verified",
  "mechanism": "Область расширений блока мира: GetFloatGameState(GAME_STATE_TIME_OF_DAY)",
  "latency": "Push-снимок",
  "signature": "time_of_day() -> 'float | None'",
  "doc": "Игровое время суток (часы, 0~24). Партия начинается в 8 утра; полные сутки = 480 игровых секунд (день и ночь по 240 секунд, масштабируются скоростью смены дня и ночи).\nЕсли прочитать не удалось (старый рантайм / не в партии), возвращает None."
 },
 {
  "name": "is_night",
  "category": "observe",
  "status": "verified",
  "mechanism": "Область расширений блока мира (день — с 6 до 18)",
  "latency": "Push-снимок",
  "signature": "is_night() -> 'bool | None'",
  "doc": "Ночь ли сейчас (18:00~6:00). Приём профи: ночью крипы спят (можно ударить первым и не попасть в окружение), у всех юнитов обзор короче (хорошее время для внезапной атаки),\nчасовые/юниты Ночных эльфов ночью становятся невидимыми у деревьев. Если прочитать не удалось, возвращает None."
 },
 {
  "name": "seconds_until",
  "category": "observe",
  "status": "verified",
  "mechanism": "Область расширений блока мира + сутки по 480 секунд (измерено: 20 игровых секунд в час)",
  "latency": "Push-снимок",
  "signature": "seconds_until(hour: 'float') -> 'float | None'",
  "doc": "Сколько игровых секунд осталось до игрового времени hour (например, seconds_until(18) = сколько осталось до темноты; удобно для планирования ночного крипинга)."
 },
 {
  "name": "items_on_ground",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок мира items[] (только лежащие на земле: дескриптор владельца — все FF)",
  "latency": "Push-снимок",
  "signature": "items_on_ground() -> 'list'",
  "doc": "Предметы на земле [Item(addr, handle_lo, handle_hi, type, x, y, life)]. При подборе/использовании приходит событие item.removed."
 },
 {
  "name": "trees",
  "category": "observe",
  "status": "verified",
  "mechanism": "Блок деревьев Local\\War3Trees_<pid> (обновляется каждые 2 секунды)",
  "latency": "Push-снимок",
  "signature": "trees(x: 'float | None' = None, y: 'float | None' = None, limit: 'int' = 60) -> 'list'",
  "doc": "Живые деревья (из DestructableData, у которых targType содержит tree); если задано (x,y), сортируются по расстоянию от ближних к дальним, не больше limit штук.\nКаждое — Tree(addr, handle_lo, handle_hi, type, x, y, life), его можно сразу передать в gather для рубки леса."
 },
 {
  "name": "events",
  "category": "observe",
  "status": "verified",
  "mechanism": "Кольцо событий Local\\War3Events_<pid> (сравнение публикаций + события урона, которые фиксирует рантайм)",
  "latency": "Push-снимок",
  "signature": "events() -> 'list'",
  "doc": "Что произошло с прошлого вызова: unit.appeared / unit.died / unit.removed / unit.damaged / order.changed /\nhero.levelup / owner.changed / item.appeared / item.removed / game.started (получаются сравнением публикаций, точность = период публикации 50 ms),\nа также damage / killed уровня движка (рантайм записывает их прямо в игровом потоке в момент удара, событие есть на **каждый удар**):\n    damage: handle = кого ударили, .source_addr = кто ударил (превратить в юнит — snapshot().unit_by_addr), .value = фактически снятое здоровье,\n            .raw_damage = урон до учёта брони, .attack_type (normal/pierce/siege/magic/chaos/hero/spell), .damage_type\n    killed: этот удар добил цель, .source_addr = убийца\nа также production.done, который рантайм получает, отслеживая таблицу производства (точность = период публикации): юнит = здание, .done_code = четырёхсимвольный код готового,\n    .done_kind = 'training' (войска/герой/воскрешение) / 'research' / 'construction' (здание построено) / 'upgrade' (улучшение ратуши/башни), .value = сколько игровых секунд заняло\nДобавлено 09-25:\n    spell.cast: юнит = заклинатель, .spell — четырёхсимвольный код способности, b — уровень, value — перезарядка в секундах, x,y — точка применения (распознаётся, когда способность уходит на перезарядку; точность = период публикации)\n    player.left: .player — номер игрока, который вышел / удалён после поражения; game.ended: выход из матча\n    selection.changed: изменилось выделение локального игрока (юнитов берите через g.selection())\n    message: строка в окне сообщений на экране (подсказка игры, чат, системное сообщение): .text — полный текст, .frame — номер окна сообщений,\n             .chat = {'channel', 'sender', 'text'} (если это чат; то, что игрок пишет в чат, читается именно отсюда)\n    ui.click / ui.hover / hotkey / mouse.world: интерфейс и ввод (g.ui), .key — key на холсте / запись горячей клавиши\nКаждое — Event(seq, kind, clock, addr, handle, type, owner, a, b, x, y, value, extra).\nВ честном режиме (fair=True) приходят только: события своих юнитов, события юнитов, видимых прямо сейчас (или ещё видимых в течение последней 1 секунды), урон по нам и урон, нанесённый нами,\nа также локальные события интерфейса / сообщений / матча."
 },
 {
  "name": "selection",
  "category": "observe",
  "status": "verified",
  "mechanism": "Область расширений блока мира W3P, selAddrs (рантайм при каждой публикации добавляет выделение локального игрока)",
  "latency": "Push-снимок",
  "signature": "selection() -> 'list'",
  "doc": "Юниты, которые сейчас выбраны у локального игрока (главный юнит — первым; не больше 12). При изменении выделения приходит событие selection.changed."
 },
 {
  "name": "messages",
  "category": "observe",
  "status": "verified",
  "mechanism": "Общая память Local\\War3Msgs_<pid> (экранные сообщения, которые фиксирует рантайм)",
  "latency": "Push-снимок",
  "signature": "messages() -> 'list'",
  "doc": "Новые строки в окнах сообщений на экране с прошлого вызова: [{'text', 'frame', 'repeat', 'seq', 'game_ms'}].\nЗдесь и подсказки игры («Нужно больше ферм», «Здесь строить нельзя»), и чат, и системные сообщения; frame показывает, в каком окне сообщений появилась строка.\nЭто те же сообщения, что и события message в потоке событий (у каждого свой курсор)."
 },
 {
  "name": "ui",
  "category": "control",
  "status": "verified",
  "mechanism": "W3P 74 input_enable + общая память Local\\War3Input_<pid> (рантайм получает ввод окна игры)",
  "latency": "Общая память",
  "signature": "ui()",
  "doc": "Интерфейс и ввод (openwar3.ui.UI): кликабельные кнопки и карточки выбора, горячие клавиши, выбор точки кликом по земле, куда указывает мышь.\nКлик по кнопке до игры не доходит; только локальный ввод + локальная отрисовка, поэтому безопасно и в многопользовательской игре."
 },
 {
  "name": "tech",
  "category": "observe",
  "status": "verified",
  "mechanism": "Запрос W3P q_tech (счётчик технологий игрока в движке)",
  "latency": "Быстрая полоса",
  "signature": "tech(code: 'str', player: 'int | None' = None) -> 'int | None'",
  "doc": "Уровень исследования / число построенных зданий (цепочка улучшений учитывается: Замок тоже считается как htow). player по умолчанию — мы, запрашивать можно любого игрока."
 },
 {
  "name": "can_do",
  "category": "observe",
  "status": "verified",
  "mechanism": "Запрос W3P q_feasible (проверка выполнимости в движке)",
  "latency": "Быстрая полоса",
  "signature": "can_do(u, code: 'str') -> 'int | None'",
  "doc": "Вердикт движка о выполнимости: 0/220 — можно; 3 пища, 8 не хватает золота, 9 не хватает древесины, 32 очередь заполнена, 183 нет нужного здания или технологии, 185 алтарь воскрешает, 221 такого нет/строится.\n⚠ Для постройки здания рабочим всегда 221 — выбирать место этим нельзя (используйте build_near)."
 },
 {
  "name": "can_do_many",
  "category": "observe",
  "status": "verified",
  "mechanism": "Запрос W3P q_feasible × N, отправка одной пачкой",
  "latency": "Быстрая полоса × 1",
  "signature": "can_do_many(pairs) -> 'list'",
  "doc": "Много can_do за раз: pairs = [(юнит, четырёхсимвольный_код), ...], возвращает список кодов вердикта в том же порядке (где ответа нет — None).\nПланируя, что строить/нанимать в этом тике, сначала спросите всё разом — это в N раз быстрее, чем can_do по одному (эталонный мозг 09-23: планирование построек 76 -> 25 ms)."
 },
 {
  "name": "tech_many",
  "category": "observe",
  "status": "verified",
  "mechanism": "Запрос W3P q_tech × N, отправка одной пачкой",
  "latency": "Быстрая полоса × 1",
  "signature": "tech_many(codes, player: 'int | None' = None) -> 'dict'",
  "doc": "Много счётчиков технологий/зданий за раз: {четырёхсимвольный_код: количество или None}."
 },
 {
  "name": "visible",
  "category": "observe",
  "status": "verified",
  "mechanism": "Запрос W3P q_visible (видно / туман войны / чёрная маска)",
  "latency": "Быстрая полоса",
  "signature": "visible(x: 'float', y: 'float') -> 'bool | None'",
  "doc": "Видим ли мы эту точку сейчас (не в тумане войны/чёрной маске). Bot в честном режиме должен использовать только видимых врагов."
 },
 {
  "name": "gold_left",
  "category": "observe",
  "status": "inferred",
  "mechanism": "Запрос W3P q_mine_gold (остаток золота в руднике по данным движка)",
  "latency": "Быстрая полоса",
  "signature": "gold_left(mine) -> 'int | None'",
  "doc": "Сколько золота осталось в руднике."
 },
 {
  "name": "enemy_ai_plan",
  "category": "observe",
  "status": "verified",
  "mechanism": "Запрос W3P q_captain (компьютерный капитан, за которым следует вражеский юнит)",
  "latency": "Быстрая полоса",
  "signature": "enemy_ai_plan(enemy_unit) -> 'dict | None'",
  "doc": "Капитан компьютерного AI: куда он ведёт войска (ещё до выхода известно, какую часть вашей базы он будет атаковать). Работает только против компьютерного противника; если юнит не следует за капитаном — None."
 },
 {
  "name": "batch",
  "category": "command",
  "status": "verified",
  "mechanism": "Команды внутри блока копятся в пачку и отправляются разом в конце блока (выполняются в одном кадре, игровой поток ждём один раз)",
  "latency": "Быстрая полоса × 1",
  "signature": "batch() -> 'Batch'",
  "doc": "Объединяет команды одного тика в пачку:\n\n    with g.batch() as b:\n        g.attack(archers, target)          # возвращает Pending, квитанцией становится после конца блока\n        g.move(wounded, *home)\n        g.cast(hero, \"thunderclap\")\n    print(b.sent, b.wait_ms, [r.reason for r in b.receipts])\n\nКаждая команда, отправленная по отдельности, ждёт одной обработки в игровом потоке (около 10 ms); пачка ждёт один раз — эталонный мозг 09-23 за счёт этого сократил раунд 48 -> 26 ms.\n* Арбитраж по-прежнему проходит по каждой команде отдельно (удерживаемый юнит сразу получает квитанцию held и в пачку не попадает);\n* команды внутри блока возвращают Pending: чтение .ok до конца блока выбрасывает ошибку (квитанции ещё нет), после конца блока он используется как Receipt;\n* исключение внутри блока = вся пачка отменяется (status 97 cancelled), удерживаемые юниты освобождаются;\n* запросы (can_do / tech / visible …), а также build_near и buy в пачку не попадают и выполняются сразу — их результат нужен немедленно;\n  чтобы спросить много за раз, используйте can_do_many / tech_many;\n* вложенный with g.batch() сливается с самой внешней пачкой; больше 16 команд рантайм автоматически делит на несколько частей (по одному ожиданию на часть)."
 },
 {
  "name": "order_of",
  "category": "observe",
  "status": "verified",
  "mechanism": "Приказ из снимка + команды, только что принятые в этом процессе (квитанции)",
  "latency": "Push-снимок",
  "signature": "order_of(u) -> 'int | None'",
  "doc": "Текущий приказ юнита, **включая отданный вами в этом тике** (пока снимок не догнал, берётся новый приказ из квитанции).\n⚠ 09-23, реальная игра: hello_bot только что отправил крестьянина строить ферму, а в том же тике rush_bot увидел в снимке, что тот «свободен», и отправил его строить казарму — ферма раз за разом бросалась на полпути.\n  Выбирая «свободные/не занятые стройкой» юниты, используйте это, а не u.order."
 },
 {
  "name": "move",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P point: move (биты extra = способ постановки в очередь)",
  "latency": "Быстрая полоса",
  "signature": "move(units, x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Идти в (x,y), не атакуя по пути (для отступления). Можно передать один юнит или список (приказ всем в одном кадре).\nqueue='after': сначала закончить текущее дело (вставить после текущего приказа). В квитанции values[0] = сколько приказов у юнита в очереди после команды (включая выполняемый)."
 },
 {
  "name": "attack_move",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P point: attack в точку",
  "latency": "Быстрая полоса",
  "signature": "attack_move(units, x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Атака с движением (A по земле): атакует врагов, встреченных по пути. queue — как в move."
 },
 {
  "name": "attack",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: команда на цель (правый клик smart)",
  "latency": "Быстрая полоса",
  "signature": "attack(units, target, force: 'bool' = False, queue: 'str | None' = None)",
  "doc": "Атаковать target. По умолчанию — правым кликом (по врагу = атаковать именно его; 09-23 измерено: и цель приказа, и текущая цель — он).\n⚠ Цель должна быть в зоне обзора, невидимую отклонят (код причины 1001).\nforce=True использует приказ атаки 0x0F (нужен, чтобы бить своих/нейтральных зверьков) — по измерениям он лишь подменяет приказ на атаку и не запоминает цель,\nи юнит уходит бить других врагов поблизости; для атаки конкретной цели его не используйте."
 },
 {
  "name": "stop",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: stop",
  "latency": "Быстрая полоса",
  "signature": "stop(units)",
  "doc": "Прекратить всё (номер приказа 0x000D0004), очередь приказов тоже очищается."
 },
 {
  "name": "hold",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: holdposition",
  "latency": "Быстрая полоса",
  "signature": "hold(units, queue: 'str | None' = None)",
  "doc": "Удерживать позицию (не преследовать, атаковать только в пределах дальности)."
 },
 {
  "name": "patrol",
  "category": "command",
  "status": "inferred",
  "mechanism": "W3P point: patrol",
  "latency": "Быстрая полоса",
  "signature": "patrol(units, x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Патрулировать между текущей позицией и (x,y)."
 },
 {
  "name": "attack_ground",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P point: attackground (осадные юниты / мортиры / катапульты)",
  "latency": "Быстрая полоса",
  "signature": "attack_ground(units, x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Атака земли: артиллерия стреляет по участку (по невидимым юнитам, по тем, кто за лесом, чтобы перекрыть проход). Принимают только юниты, способные атаковать землю."
 },
 {
  "name": "cancel",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: cancel",
  "latency": "Быстрая полоса",
  "signature": "cancel(building)",
  "doc": "Отмена: последняя ячейка очереди найма/исследований (деньги возвращаются), строящееся здание (возврат 75%), улучшающаяся ратуша."
 },
 {
  "name": "path",
  "category": "command",
  "status": "verified",
  "mechanism": "Пачка: первый отрезок выполняется сразу, остальные вставляются в обратном порядке через queue='after' (движок умеет вставлять только после текущего)",
  "latency": "Быстрая полоса × 1",
  "signature": "path(units, points, attack: 'bool' = False)",
  "doc": "Пройти по цепочке точек по порядку (точки через Shift: маршрутные точки, обход башен, маршрут разведки). attack=True — каждый отрезок как атака с движением.\nОтправляется за один раз; квитанций — по одной на точку (в порядке points)."
 },
 {
  "name": "gather",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: harvest (золотой рудник или дерево)",
  "latency": "Быстрая полоса",
  "signature": "gather(workers, target, queue: 'str | None' = None)",
  "doc": "Добыча золота/древесины (target — золотой рудник или дерево из trees()). ⚠ Отправляйте только свободных рабочих (idle_workers): повторный приказ рабочему с заданием прерывает цикл добычи.\nКак используют профи: вернуться на рудник после стройки = после build(...) вызвать gather(worker, mine, queue='after')."
 },
 {
  "name": "repair",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: repair",
  "latency": "Быстрая полоса",
  "signature": "repair(workers, building, queue: 'str | None' = None)",
  "doc": "Ремонт / помощь в строительстве (стройка Людей и Орды без строителя замирает)."
 },
 {
  "name": "build",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P build: приказ постройки, приказ рабочего перечитывается в том же кадре для подтверждения",
  "latency": "Быстрая полоса",
  "signature": "build(worker, code: 'str', x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Рабочий строит code в (x,y) (координаты выравниваются по сетке 32). Квитанция «принята» = приказ рабочего уже — это здание (или приказ начала стройки);\nпри queue='after' = поставлено в очередь приказов рабочего (values[0] в квитанции — длина очереди).\n⚠ «Принята» ≠ «построено»: точку в лесу движок тоже принимает сразу, а неудача наступает, когда рабочий дойдёт (измерено 09-23); если деньги потрачены в другом месте, фундамент тоже не появится.\nЕсли не знаете, где есть место, используйте build_near (он отслеживает результат и заносит неудачные точки в чёрный список). Для нескольких зданий подряд — build_queue."
 },
 {
  "name": "build_queue",
  "category": "command",
  "status": "verified",
  "mechanism": "Пачка: первое здание сразу, остальные в обратном порядке через queue='after'",
  "latency": "Быстрая полоса × 1",
  "signature": "build_queue(worker, plan)",
  "doc": "Один рабочий строит несколько зданий подряд (стройка через Shift): plan = [(четырёхсимвольный_код, x, y), ...]. Отправляется за один раз; квитанции — в порядке plan.\n⚠ Деньги списываются только в момент начала стройки (не при постановке в очередь) — если поставили 3 здания, а денег хватает на 1, последние два сорвутся, когда рабочий до них дойдёт."
 },
 {
  "name": "build_near",
  "category": "command",
  "status": "verified",
  "mechanism": "Команда build по точкам + отслеживание (появился фундамент = успех; рабочий бросил приказ, а фундамента нет = точка в чёрный список)",
  "latency": "Быстрая полоса × число пробных точек",
  "signature": "build_near(worker, code: 'str', x: 'float', y: 'float', min_r: 'float' = 450, max_r: 'float' = 1500, max_tries: 'int' = 24)",
  "doc": "Ищет вокруг (x,y) от ближнего к дальнему место, куда влезет code, и строит. **Не блокирует**, можно вызывать каждый тик:\n  * для этого здания уже идёт попытка (рабочий в пути) -> возвращает ту же точку, приказ не повторяется;\n  * прошлая попытка удалась (появился фундамент) -> при необходимости ищет новую точку;\n  * прошлая попытка провалилась (рабочий дошёл и обнаружил, что места нет; движок снял приказ, фундамента нет) -> точка попадает в чёрный список на 45 секунд, берётся следующая;\n  * не хватает денег -> сразу возвращает None (не пробует и в чёрный список не заносит); все точки перепробованы — возвращает None.\n⚠ Зачем отслеживать: 09-23, реальная игра — точку в лесу движок **сразу принимает**, а неудача случается, только когда рабочий дойдёт (по квитанции того же кадра этого не понять);\n  а проверка места в движке для постройки рабочим всегда возвращает 221, так что «сначала проверить, потом строить» тоже не выйдет. Сразу отклоняются только явно занятые точки (центр ратуши)."
 },
 {
  "name": "can_afford",
  "category": "observe",
  "status": "verified",
  "mechanism": "Наши ресурсы из push-снимка + цены из units.json",
  "latency": "Push-снимок",
  "signature": "can_afford(code: 'str') -> 'bool'",
  "doc": "Хватает ли сейчас золота/древесины на code (юниты, здания; по ценам из units.json). Всё, чего нет в таблице цен, считается доступным.\n⚠ У четырёхсимвольных кодов улучшения ратуши в таблице накопленная цена, так что оценка здесь будет консервативной; окончательное слово — за квитанцией движка."
 },
 {
  "name": "train",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: четырёхсимвольный код; при отказе — с кодом причины из проверки выполнимости",
  "latency": "Быстрая полоса",
  "signature": "train(building, code: 'str')",
  "doc": "Нанять юнит / исследовать технологию / улучшить ратушу (улучшение = приказ самой ратуше с четырёхсимвольным кодом целевого здания, например 'hkee').\nПри отказе reason в квитанции объяснит почему (не хватает пищи, золота, древесины, очередь заполнена, нет нужного здания или технологии…)."
 },
 {
  "name": "learn",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P learn: изучено, только если уменьшились очки навыков",
  "latency": "Быстрая полоса",
  "signature": "learn(hero, ability: 'str')",
  "doc": "Герой изучает способность (четырёхсимвольный код, например 'AHbz' — Снежная буря)."
 },
 {
  "name": "cast",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target / point / immediate (выбирается по аргументам)",
  "latency": "Быстрая полоса",
  "signature": "cast(u, spell, target=None, x: 'float | None' = None, y: 'float | None' = None)",
  "doc": "Применить способность. spell — строка приказа ('thunderbolt' — Молот бурь, 'blizzard', 'holybolt' — Свет небес…, см. data/order-ids.txt) или номер приказа.\nЗадан target — на юнит; заданы x,y — на землю; ничего не задано — без цели (Раскат грома, Божественный щит, призыв Элементаля воды).\n«Принята» в квитанции означает лишь, что движок принял команду; применилась ли способность, смотрите по тому, ушла ли она на перезарядку в cooldown() и появился ли бафф в buffs()."
 },
 {
  "name": "rally",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P rally",
  "latency": "Быстрая полоса",
  "signature": "rally(building, x: 'float | None' = None, y: 'float | None' = None, target=None)",
  "doc": "Задать точку сбора (в точку либо на юнит/золотой рудник)."
 },
 {
  "name": "revive",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P revive: список погибших героев -> алтарь применяет воскрешение к погибшему герою",
  "latency": "Быстрая полоса",
  "signature": "revive(altar, hero=None)",
  "doc": "Воскресить погибшего героя в алтаре (если hero не задан — первого в списке).\nЧастые причины отказа (будут в reason квитанции): не хватает пищи (герой тоже занимает пищу), не хватает денег, герой погиб слишком недавно (воскрешать можно примерно через 3 игровые секунды после смерти),\nвоскрешение уже идёт (при принятии движок сразу очищает этот слот)."
 },
 {
  "name": "pick_up",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: правый клик по предмету",
  "latency": "Быстрая полоса",
  "signature": "pick_up(hero, item)",
  "doc": "Герой идёт подбирать предмет с земли (item — из items_on_ground). После подбора предмет появляется в инвентаре, а для земли приходит событие item.removed."
 },
 {
  "name": "use_item",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P use_item (по номеру ячейки)",
  "latency": "Быстрая полоса",
  "signature": "use_item(hero, slot: 'int', target=None, x: 'float | None' = None, y: 'float | None' = None)",
  "doc": "Использовать предмет из ячейки инвентаря slot (0~5); можно указать целевой юнит или целевую точку.\n⚠ При использовании предмета в точку (например, Башни из слоновой кости) движок и при успехе возвращает 0, так что квитанция всегда «принята» — проверяйте, опустела ли ячейка."
 },
 {
  "name": "drop_item",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P item_drop (копия JASS UnitDropItemPoint: dropitem 0xD0021 в точку + предмет как мгновенная цель)",
  "latency": "Быстрая полоса",
  "signature": "drop_item(hero, slot: 'int', x: 'float', y: 'float')",
  "doc": "Выложить предмет из ячейки slot в (x,y) (герой подходит и кладёт)."
 },
 {
  "name": "give_item",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P item_drop (копия JASS UnitDropItemTarget: dropitem на юнит)",
  "latency": "Быстрая полоса",
  "signature": "give_item(hero, slot: 'int', to)",
  "doc": "Отдать предмет из ячейки slot юниту to (другому герою / юниту — подойти и передать). Отдать магазину = продать (см. sell_item)."
 },
 {
  "name": "sell_item",
  "category": "command",
  "status": "verified",
  "mechanism": "Как give_item, но цель — магазин (измерено: Staff of Sanctuary продаётся за 125 золота)",
  "latency": "Быстрая полоса",
  "signature": "sell_item(hero, slot: 'int', shop)",
  "doc": "Продать предмет из ячейки slot магазину (герой должен подойти к магазину; принимаются только продаваемые предметы, возвращается половина цены)."
 },
 {
  "name": "move_item",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: приказ 0xD0022+номер ячейки, цель = предмет (копия JASS UnitDropItemSlot)",
  "latency": "Быстрая полоса",
  "signature": "move_item(hero, slot: 'int', to_slot: 'int')",
  "doc": "Переложить предмет в инвентаре (из ячейки slot в ячейку to_slot; если заняты обе — поменять местами). Для раскладки под горячие клавиши."
 },
 {
  "name": "buy",
  "category": "command",
  "status": "inferred",
  "mechanism": "W3P buy: магазин продаёт предмет стоящему рядом герою",
  "latency": "Быстрая полоса",
  "signature": "buy(shop, item_code: 'str')",
  "doc": "Купить предмет в магазине (для героя, стоящего рядом с магазином). Если не хватает требуемой технологии, движок возвращает 0 и деньги не списывает."
 },
 {
  "name": "call_to_arms",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: townbellon/off",
  "latency": "Быстрая полоса",
  "signature": "call_to_arms(hall, on: 'bool' = True)",
  "doc": "Призыв к оружию у Людей: крестьяне превращаются в Ополчение (у Ратуши первого уровня этой способности нет — работает только у Крепости/Замка)."
 },
 {
  "name": "set_speed",
  "category": "control",
  "status": "verified",
  "mechanism": "Действие 47 (25~800%)",
  "latency": "Канал управления",
  "signature": "set_speed(percent: 'int') -> 'bool'",
  "doc": "Скорость игры (100 = обычная)."
 },
 {
  "name": "pause",
  "category": "control",
  "status": "verified",
  "mechanism": "W3P pause",
  "latency": "Быстрая полоса",
  "signature": "pause(on: 'bool' = True)",
  "doc": "Поставить игру на паузу / снять с паузы. На паузе часы движка стоят, но через быструю полосу приказы отдавать можно (диспетчеризация событий продолжает работать)."
 },
 {
  "name": "set_publish_period",
  "category": "control",
  "status": "verified",
  "mechanism": "requestedPeriodMs блока мира",
  "latency": "Push-снимок",
  "signature": "set_publish_period(ms: 'int') -> 'None'",
  "doc": "Период публикации состояния мира (16~1000 миллисекунд, по умолчанию 50). Один сбор — около 0.5 ms, так что и 33 ms не проблема; значение одно на всю машину, действует последнее записанное."
 },
 {
  "name": "say",
  "category": "control",
  "status": "verified",
  "mechanism": "Действие 56",
  "latency": "Канал управления",
  "signature": "say(u, text: 'str', seconds: 'float' = 4.0) -> 'bool'",
  "doc": "Показать облачко чата над юнитом (для трансляций/отладки, на игру не влияет). Если облачко не появилось, возвращает False; причина — в g.last_say_error."
 },
 {
  "name": "message",
  "category": "control",
  "status": "inferred",
  "mechanism": "Действие 45",
  "latency": "Канал управления",
  "signature": "message(text: 'str') -> 'bool'",
  "doc": "Вывести строку в области сообщений в левом нижнем углу игры (видно только на этой машине). Сначала игра должна сама показать хотя бы одну подсказку (DLL перехватывает окно сообщений именно в тот момент)."
 },
 {
  "name": "end_game",
  "category": "control",
  "status": "verified",
  "mechanism": "Действие 22",
  "latency": "Канал управления",
  "signature": "end_game() -> 'bool'",
  "doc": "Завершить этот процесс игры (farm.py --keep автоматически начнёт следующую партию согласно next_game.json)."
 },
 {
  "name": "canvas",
  "category": "control",
  "status": "verified",
  "mechanism": "W3P 73 canvas_enable + общая память Local\\War3Canvas_<pid> (рантайм рисует каждый кадр перед тем, как игра рисует указатель мыши; указатель перекрывает холст)",
  "latency": "Общая память",
  "signature": "canvas()",
  "doc": "Холст: рисует поверх игрового экрана текстовые блоки, панели, индикаторы прогресса, изображения, круги и маршруты на земле (openwar3.canvas.Canvas).\nРисует сам рантайм, не создаёт игровых дескрипторов и не меняет состояние игры — поэтому безопасен и в многопользовательской игре; стиль любой (китайский текст, скругления, полупрозрачность)."
 },
 {
  "name": "press_to_continue",
  "category": "control",
  "status": "verified",
  "mechanism": "PostMessage WM_KEYDOWN/UP пробела окну игры (фокус не перехватывается)",
  "latency": "Сообщение окну",
  "signature": "press_to_continue() -> 'bool'",
  "doc": "Нажать пробел на экране загрузки «Нажмите любую клавишу, чтобы продолжить». Многие RPG / сюжетные карты после загрузки ждут нажатия клавиши (проверено 09-24 на WarChasers:\nбез нажатия игра так и стоит на экране загрузки, игровые часы на 0, быстрая полоса не опустошается). openwar3.run нажимает сам, пока ждёт входа в партию, так что вызывать вручную обычно не нужно."
 },
 {
  "name": "map_data",
  "category": "observe",
  "status": "verified",
  "mechanism": "Файл карты (путь из --map лаунчера): w3u/w3t/w3a + wts; у защищённых карт читаются TXT внутри карты",
  "latency": "Чтение файла (в первый раз ≈ 0.1 s)",
  "signature": "map_data()",
  "doc": "Данные карты текущей партии (openwar3.mapdata.MapData): name_of('HC07') — имена пользовательских юнитов/предметов/способностей, hero_names, tooltip.\nБольшинство юнитов на RPG-картах созданы самой картой, во встроенной таблице имён их нет; если игра запущена не лаунчером (файл карты не найден), возвращает None."
 },
 {
  "name": "jass",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "W3P 70 jass (рантайм ищет native по имени в таблице, всего 1291)",
  "latency": "Быстрая полоса",
  "signature": "jass()",
  "doc": "Вызвать любую JASS native по имени: g.jass.CreateUnit(g.jass.Player(1), \"Hpal\", x, y, 270.0).\nАргументы I/R/B/S/H преобразуются автоматически (объекты юнитов/предметов передаются как есть); в многопользовательской игре можно вызывать только функции чтения. Подробнее — openwar3/jass.py и docs/COMPANION_ZH.md."
 },
 {
  "name": "player_slots",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS GetPlayerController / GetPlayerSlotState / IsPlayerAlly",
  "latency": "Быстрая полоса",
  "signature": "player_slots() -> 'list[dict]'",
  "doc": "16 слотов игроков: controller (user — живой игрок / computer / neutral…), state (empty / playing / left), human, me, ally (союзник ли нам).\nНа RPG-картах через него ищут свободный слот для компаньона и определяют, одиночная ли это игра."
 },
 {
  "name": "spawn",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS CreateUnit + W3P 72 дескриптор -> юнит",
  "latency": "Быстрая полоса",
  "signature": "spawn(code: 'str', x: 'float', y: 'float', player: 'int | None' = None, facing: 'float' = 270.0)",
  "doc": "Создать юнит в (x,y) (player по умолчанию — локальный игрок) и вернуть юнит из снимка (после следующей публикации мира, ≈ 50 ms); если создать не удалось, возвращает None.\nУ возвращённого юнита есть дополнительное свойство jass_handle. ⚠ Работает только в одиночной игре (в многопользовательской — рассинхронизация)."
 },
 {
  "name": "set_alliance",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS SetPlayerAlliance",
  "latency": "Быстрая полоса",
  "signature": "set_alliance(a: 'int', b: 'int', allied: 'bool' = True, vision: 'bool' = True, control: 'bool' = False, xp: 'bool' = False, both: 'bool' = True) -> 'None'",
  "doc": "Задать отношение игрока a к игроку b: allied = не атакуют друг друга + приходят друг другу на помощь; vision — общий обзор; control — общее управление юнитами (b может командовать юнитами a);\nxp — общий опыт. both=True задаёт оба направления сразу (control — только a -> b)."
 },
 {
  "name": "set_player_name",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS SetPlayerName",
  "latency": "Быстрая полоса",
  "signature": "set_player_name(player: 'int', name: 'str') -> 'None'",
  "doc": "Сменить имя игрока (то, что видно в таблице счёта, в чате и на панели союзников). Нужно, чтобы дать имя компаньону."
 },
 {
  "name": "show_text",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS DisplayTimedTextToPlayer",
  "latency": "Быстрая полоса",
  "signature": "show_text(text: 'str', seconds: 'float' = 6.0, player: 'int | None' = None) -> 'None'",
  "doc": "Показать строку текста в левом нижнем углу экрана (такой текст выводят триггеры карты), по умолчанию — локальному игроку. Поддерживаются цветовые коды |cffRRGGBB."
 }
]