[
 {
  "name": "status",
  "category": "meta",
  "status": "verified",
  "mechanism": "Weltblock + Schnellspur + Claim-Tabelle",
  "latency": "Lokal berechnet",
  "signature": "status() -> 'dict'",
  "doc": "Verbindungsstatus: pid, Weltveröffentlichung (Periode, Erfassungsdauer), Zähler der Schnellspur."
 },
 {
  "name": "snapshot",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Weltblock Local\\War3World_<pid> (die Runtime pusht alle 50 ms, seqlock)",
  "latency": "Gepushter Snapshot",
  "signature": "snapshot(max_age: 'float' = 0.05)",
  "doc": "Vollständiger Zustand der gesamten Karte (WorldState): .units .players .items .clock .me; wiederholte Aufrufe innerhalb von max_age Sekunden liefern dieselbe Kopie.\n⚠ Arbeiter in einer Goldmine sind nicht in der Liste; standardmäßig ist die ganze Karte sichtbar (im Lockstep-Modell liegt lokal alles vor), erst Game(fair=True) filtert nach Sichtweite."
 },
 {
  "name": "last_seen",
  "category": "observe",
  "status": "verified",
  "mechanism": "visibleTo des Push-Snapshots (bei jeder Aktualisierung des Snapshots werden sichtbare gegnerische Einheiten/Creeps festgehalten)",
  "latency": "Gepushter Snapshot",
  "signature": "last_seen(owner: 'str | int' = 'enemy', max_age: 'float | None' = None) -> 'list'",
  "doc": "Zuletzt gesehene Einheiten des Gegners (oder 'creep' für Creeps, oder einer Spielernummer): [(Zustand der Einheit damals, Spieluhr damals, vergangene Sekunden)], neueste zuerst.\nSieht man eine Einheit sterben, fliegt sie aus der Liste. Fair-Modus und normaler Modus erfassen beide nach \"was wir gerade sehen\" – das ist die Karte im Kopf eines Spielers:\naufgeklärte Truppenstärke, wo der gegnerische Held zuletzt war, wann der Gegner seine Expansion gebaut hat. max_age: nur Einträge aus so vielen zurückliegenden Spielsekunden."
 },
 {
  "name": "map",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Kartenblock Local\\War3Map_<pid> (die Runtime berechnet ihn nach Partiebeginn in Etappen, IsTerrainPathable Laufen/Bauen)",
  "latency": "Gepushter Snapshot",
  "signature": "map()",
  "doc": "Geländetabelle dieser Partie, MapInfo: .walkable(x,y) .buildable(x,y) .at(x,y) .bounds (spielbarer Bereich) .starts (Startpositionen) .cells (bit0 nicht begehbar, bit1 nicht bebaubar).\nNach Partiebeginn dauert die Berechnung einige Sekunden; bis dahin None. Bäume sind nicht enthalten (dafür trees())."
 },
 {
  "name": "me",
  "category": "observe",
  "status": "verified",
  "mechanism": "Header des Weltblocks",
  "latency": "Gepushter Snapshot",
  "signature": "me() -> 'int | None'",
  "doc": "Welche Spielernummer ich habe (0~11)."
 },
 {
  "name": "resources",
  "category": "observe",
  "status": "verified",
  "mechanism": "Weltblock players[16]",
  "latency": "Gepushter Snapshot",
  "signature": "resources(player: 'int | None' = None) -> 'dict | None'",
  "doc": "{'gold','lumber','food_used','food_cap','gold_gathered','lumber_gathered'}; player ist standardmäßig der eigene Spieler, jeder Spieler ist lesbar.\nNicht lesbar = None, nicht als 0 behandeln."
 },
 {
  "name": "players",
  "category": "observe",
  "status": "verified",
  "mechanism": "Weltblock players[16]",
  "latency": "Gepushter Snapshot",
  "signature": "players() -> 'list'",
  "doc": "Alle 16 Spieler-Slots: Player(id, gold, lumber, food_used, food_cap, gold_gathered, lumber_gathered, race, known)."
 },
 {
  "name": "units",
  "category": "observe",
  "status": "verified",
  "mechanism": "Weltblock units[]",
  "latency": "Gepushter Snapshot",
  "signature": "units(owner: 'str | int' = 'all', types=None, alive: 'bool' = True) -> 'list'",
  "doc": "Einheiten nach Besitzer/Typ filtern. owner: 'me' / 'enemy' / 'creep' / 'all' / Spielernummer. types: Menge von Vier-Zeichen-Codes."
 },
 {
  "name": "unit",
  "category": "observe",
  "status": "verified",
  "mechanism": "Weltblock by_handle",
  "latency": "Gepushter Snapshot",
  "signature": "unit(handle) -> 'object | None'",
  "doc": "Einheit über das Handle-Paar (lo, hi) finden (Order-Ziele, Aufgabenziele und Events liefern alle Handle-Paare)."
 },
 {
  "name": "is_building",
  "category": "observe",
  "status": "verified",
  "mechanism": "Snapshot + units.json (spd==0 = Gebäude)",
  "latency": "Gepushter Snapshot",
  "signature": "is_building(u) -> 'bool'",
  "doc": "Ob es ein Gebäude ist (inkl. Türme). Entschieden wird über Bewegungsgeschwindigkeit 0 laut Einheitentabelle; das Hauptgebäude der Untoten hat eine Grundfläche von 0, also nicht über die Grundfläche entscheiden."
 },
 {
  "name": "my_workers",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot",
  "latency": "Gepushter Snapshot",
  "signature": "my_workers() -> 'list'",
  "doc": "Eigene Arbeiter (Bauer/Peon/Akolyth/Irrlicht)."
 },
 {
  "name": "idle_workers",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot (Order-Slot + Aufgaben-Slot)",
  "latency": "Gepushter Snapshot",
  "signature": "idle_workers() -> 'list'",
  "doc": "Arbeiter ohne Beschäftigung: keine Order und keine Aufgabe (wem du in diesem Tick gerade etwas zugewiesen hast, zählt nicht).\n⚠ Gibst du einem Arbeiter mit Aufgabe erneut einen Sammelbefehl, unterbricht das den Sammelzyklus (Einkommen fällt auf null)."
 },
 {
  "name": "my_heroes",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot",
  "latency": "Gepushter Snapshot",
  "signature": "my_heroes() -> 'list'",
  "doc": "Eigene lebende Helden (tote stehen in der Wiederbelebungsliste des Altars, siehe revive)."
 },
 {
  "name": "my_army",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot + units.json",
  "latency": "Gepushter Snapshot",
  "signature": "my_army() -> 'list'",
  "doc": "Eigene Kampfeinheiten: keine Arbeiter, keine Gebäude."
 },
 {
  "name": "my_buildings",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot",
  "latency": "Gepushter Snapshot",
  "signature": "my_buildings(types=None) -> 'list'",
  "doc": "Eigene Gebäude (inkl. Türme und Baustellen); über types lassen sich bestimmte Arten auswählen, z. B. {'hbar'}."
 },
 {
  "name": "is_constructing",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot (Order = Vier-Zeichen-Code eines Gebäudes, oder Bau-/Reparaturbefehl)",
  "latency": "Gepushter Snapshot",
  "signature": "is_constructing(worker) -> 'bool'",
  "doc": "Ob dieser Arbeiter gerade baut (oder zum Bauplatz läuft / beim Reparieren hilft; inkl. gerade in diesem Tick zugewiesener). Bei der Wahl eines Bauarbeiters überspringen, sonst steht die vorige Baustelle still."
 },
 {
  "name": "under_construction",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot (die TP einer Baustelle steigen von sehr niedrig bis voll)",
  "latency": "Gepushter Snapshot",
  "signature": "under_construction(building) -> 'bool'",
  "doc": "Dieses Gebäude ist noch nicht fertig (TP nicht voll). ⚠ Beschädigte Gebäude haben ebenfalls keine vollen TP – für die Eröffnung reicht das, sobald gekämpft wird, zusätzlich die Zeit berücksichtigen."
 },
 {
  "name": "gold_mines",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot (ngol/egol/ugol)",
  "latency": "Gepushter Snapshot",
  "signature": "gold_mines() -> 'list'",
  "doc": "Goldminen auf der Karte. ⚠ Die Entangled Gold Mine der Nachtelfen und die neutrale Goldmine sind zwei Einheiten an derselben Koordinate; zum Sammeln die eigene zuweisen."
 },
 {
  "name": "enemies",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot",
  "latency": "Gepushter Snapshot",
  "signature": "enemies(fighters_only: 'bool' = False) -> 'list'",
  "doc": "Einheiten gegnerischer Spieler (ohne Creeps). fighters_only: Arbeiter und Gebäude weglassen."
 },
 {
  "name": "creeps",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot (owner 12 = neutral feindselig)",
  "latency": "Gepushter Snapshot",
  "signature": "creeps() -> 'list'",
  "doc": "Creeps (neutral feindselig). ⚠ Nachts sinkt die Sichtweite; liegen entfernte Lager im Kriegsnebel, werden Zielbefehle auf sie abgelehnt (Grundcode 1001)."
 },
 {
  "name": "nearest",
  "category": "meta",
  "status": "verified",
  "mechanism": "Reine Berechnung",
  "latency": "Lokal berechnet",
  "signature": "nearest(candidates, to)",
  "doc": "Der Kandidat, der to (Einheit oder (x,y)) am nächsten ist; ohne Kandidaten None."
 },
 {
  "name": "life_mana",
  "category": "observe",
  "status": "verified",
  "mechanism": "Einheit im Weltblock: hp/hpMax/mana/manaMax",
  "latency": "Gepushter Snapshot",
  "signature": "life_mana(u) -> 'dict | None'",
  "doc": "{'hp','hp_max','mana','mana_max'} (Float, Rohwerte der Engine). Für u reicht die Einheit aus dem Snapshot (sie wird durch die neueste Kopie ersetzt)."
 },
 {
  "name": "hero_info",
  "category": "observe",
  "status": "verified",
  "mechanism": "Einheit im Weltblock: level/xp/skillPoints",
  "latency": "Gepushter Snapshot",
  "signature": "hero_info(hero) -> 'dict | None'",
  "doc": "{'level','xp','skill_points'}."
 },
 {
  "name": "abilities",
  "category": "observe",
  "status": "verified",
  "mechanism": "Details im Weltblock: Fähigkeiten (Code/Stufe/Flags/verbleibende Abklingzeit)",
  "latency": "Gepushter Snapshot",
  "signature": "abilities(u) -> 'list'",
  "doc": "[{code, level, cooldown, flags}]; Buffs liefert buffs(u). Nur für Einheiten \"mit Details\" (Helden > Spielereinheiten > Creeps, bis zu 256)."
 },
 {
  "name": "buffs",
  "category": "observe",
  "status": "verified",
  "mechanism": "Details im Weltblock: Fähigkeitsobjekte, die mit B beginnen",
  "latency": "Gepushter Snapshot",
  "signature": "buffs(u) -> 'list'",
  "doc": "Buff-Codes auf der Einheit (z. B. 'BHds' Gottesschild, 'Bslo' Verlangsamen). Welcher Effekt zu welchem Code gehört, steht in data/game/buffs.json."
 },
 {
  "name": "cooldown",
  "category": "observe",
  "status": "verified",
  "mechanism": "Details im Weltblock: verbleibende Abklingzeit der Fähigkeit (Fähigkeits-Timer)",
  "latency": "Gepushter Snapshot",
  "signature": "cooldown(u, ability: 'str') -> 'float | None'",
  "doc": "Wie viele Sekunden (Spielsekunden) diese Fähigkeit noch abklingen muss; 0 = einsatzbereit; Fähigkeit nicht vorhanden (oder Einheit ohne Details) = None."
 },
 {
  "name": "inventory",
  "category": "observe",
  "status": "verified",
  "mechanism": "Details im Weltblock: 6 Inventarplätze",
  "latency": "Gepushter Snapshot",
  "signature": "inventory(hero) -> 'list | None'",
  "doc": "Vier-Zeichen-Codes der 6 Gegenstandsplätze (leere Plätze = None); ohne Inventar None."
 },
 {
  "name": "current_order",
  "category": "observe",
  "status": "verified",
  "mechanism": "Einheit im Weltblock: order / Order-Ziel / Order-Zielpunkt",
  "latency": "Gepushter Snapshot",
  "signature": "current_order(u) -> 'dict | None'",
  "doc": "{'order','target','x','y'}: die aktuelle Order der Einheit (order ist 0x000D00xx oder der Vier-Zeichen-Code eines Gebäudes, 0 = untätig).\ntarget ist ein Handle-Paar; mit g.unit(target) bekommst du die Einheit."
 },
 {
  "name": "current_target",
  "category": "observe",
  "status": "verified",
  "mechanism": "Aufgabenziel der Einheit im Weltblock",
  "latency": "Gepushter Snapshot",
  "signature": "current_target(u)",
  "doc": "Die Einheit, die diese Einheit **tatsächlich angreift/verfolgt** (sonst None).\n⚠ Nach einem Angriffsbefehl wird der Order-Slot schnell leer, der Angriff hängt an der Aufgabe – um zu prüfen, \"wen sie angreift\", diese Funktion nehmen, nicht current_order."
 },
 {
  "name": "clock",
  "category": "observe",
  "status": "verified",
  "mechanism": "Header des Weltblocks: clockMs (Spieluhr der Engine)",
  "latency": "Gepushter Snapshot",
  "signature": "clock() -> 'float | None'",
  "doc": "Spieluhr der Engine (Spielsekunden, 0 während des Ladens). Bei erhöhter Spielgeschwindigkeit läuft sie schneller als die Echtzeit."
 },
 {
  "name": "production",
  "category": "observe",
  "status": "verified",
  "mechanism": "Produktionstabelle im Weltblock (Fähigkeitsobjekte Aque/ABnP/AUnP + von der Runtime verfolgte vergangene Zeit; gemessener Fehler < 0.2 Spielsekunden)",
  "latency": "Gepushter Snapshot",
  "signature": "production(building)",
  "doc": "Was dieses Gebäude gerade tut: Production(kind, queue, duration, elapsed, blocked, progress, remaining…); tut es nichts, None.\n  kind 'queue' (Training/Forschung/Held, queue höchstens 7 Plätze, [0] ist das aktuelle) / 'construction' (im Bau) / 'upgrade' (Hauptgebäude/Turm wird aufgewertet);\n  blocked = eingereiht, aber nicht gestartet (meist zu wenig Nahrung – Zeit für einen Bauernhof); progress 0..1.\nAuch gegnerische Gebäude sind lesbar (im Fair-Modus nur sichtbare Gebäude)."
 },
 {
  "name": "queue",
  "category": "observe",
  "status": "verified",
  "mechanism": "Produktionstabelle im Weltblock",
  "latency": "Gepushter Snapshot",
  "signature": "queue(building) -> 'list'",
  "doc": "Vier-Zeichen-Codes in der Trainings-/Forschungswarteschlange ([0] ist in Arbeit); untätig oder kein Produktionsgebäude = []."
 },
 {
  "name": "all_production",
  "category": "observe",
  "status": "verified",
  "mechanism": "Produktionstabelle im Weltblock",
  "latency": "Gepushter Snapshot",
  "signature": "all_production(owner: 'str | int' = 'me') -> 'list'",
  "doc": "Alle laufenden Produktionen [(Gebäude, Production)]. owner wie bei units(): 'me' / 'enemy' / Spielernummer / 'all'.\nProfi-Nutzung: sehen, welche Einheiten der Gegner trainiert, welche Technologien er erforscht und wann er seinen Tier-up macht (sobald seine Gebäude aufgeklärt sind)."
 },
 {
  "name": "path_distance",
  "category": "observe",
  "status": "verified",
  "mechanism": "Kartenblock (Engine IsTerrainPathable) + Baumblock + Grundfläche der Gebäude, A* auf SDK-Seite (128 pro Zelle)",
  "latency": "Gepushter Snapshot + lokale Berechnung",
  "signature": "path_distance(a, b) -> 'float | None'",
  "doc": "Laufweg einer Bodeneinheit von a nach b (a, b als Einheit oder (x,y)); unerreichbar = None. Auf Inselkarten damit prüfen, \"ob man zu diesem Creep-Lager/dieser Expansion auf dem Landweg kommt\" –\nzuverlässiger als die Luftlinie (um Wälder, Klippen und Gebäude herum). Genauigkeit eine Zelle (128); Lücken schmaler als eine Zelle gelten als unpassierbar."
 },
 {
  "name": "reachable",
  "category": "observe",
  "status": "verified",
  "mechanism": "Wie oben",
  "latency": "Gepushter Snapshot + lokale Berechnung",
  "signature": "reachable(a, b) -> 'bool | None'",
  "doc": "Ob das Ziel am Boden erreichbar ist (Kartenblock noch nicht berechnet = None)."
 },
 {
  "name": "walk_path",
  "category": "observe",
  "status": "verified",
  "mechanism": "Wie oben",
  "latency": "Gepushter Snapshot + lokale Berechnung",
  "signature": "walk_path(a, b) -> 'list | None'",
  "doc": "Knickpunkte des Wegs [(x,y)...] (der letzte Punkt ist b); zusammen mit path(units, Punktliste) folgt die Armee diesem Weg (Türme umgehen, Nebenwege nehmen)."
 },
 {
  "name": "upkeep",
  "category": "observe",
  "status": "inferred",
  "mechanism": "Feste Regel in 1.27: 0~50 Nahrung kein Unterhalt, 51~80 Einkommen ×0.7, 81~100 ×0.4",
  "latency": "Gepushter Snapshot",
  "signature": "upkeep(player: 'int | None' = None) -> 'dict | None'",
  "doc": "Unterhaltsstufe: {'level': 'none'/'low'/'high', 'income': 1.0/0.7/0.4, 'next_at': Nahrung der nächsten Stufe (keine = None)}.\nProfi-Wissen: Beim Tier-up auf Stufe 3 und bei Angriffs-/Rüstungs-Upgrades bei 50 Nahrung bleiben, erst vor der Entscheidungsschlacht auf 80 gehen."
 },
 {
  "name": "xp_to_next",
  "category": "observe",
  "status": "verified",
  "mechanism": "Weltblock level/xp + Formel NeedHeroXP aus MiscGame",
  "latency": "Gepushter Snapshot",
  "signature": "xp_to_next(hero) -> 'int | None'",
  "doc": "Wie viele EP dem Helden bis zur nächsten Stufe fehlen (Stufe 10 = 0)."
 },
 {
  "name": "creep_camps",
  "category": "observe",
  "status": "verified",
  "mechanism": "Push-Snapshot (Creeps im Abstand von 600 zu einer Gruppe verbunden) + Stufen aus units.json",
  "latency": "Gepushter Snapshot",
  "signature": "creep_camps(link: 'float' = 600.0) -> 'list'",
  "doc": "Fasst die (sichtbaren) Creeps auf der Karte zu Lagern zusammen: [{'x','y','units','level','hp','max_level'}], sortiert nach Entfernung zu unserer Hauptbasis, nah zuerst.\nlevel = Gesamtstufe des Lagers (das übliche Maß für die Schwierigkeit beim Creepen), hp = Gesamt-TP. Zusammen mit time_to_kill / path_distance ein Lager auswählen."
 },
 {
  "name": "buff_info",
  "category": "observe",
  "status": "verified",
  "mechanism": "data/game/buffs.json (BuffID aus AbilityData.slk -> Fähigkeit/Effekt/Dauer)",
  "latency": "Lokale Daten",
  "signature": "buff_info(code: 'str') -> 'dict | None'",
  "doc": "Was ein Buff-Code ist: {'ability','effect','dur','hero_dur','targets'} (z. B. 'Bslo' -> Verlangsamen). Bei mehreren Zeilen pro Code wird die erste geliefert."
 },
 {
  "name": "stats",
  "category": "observe",
  "status": "verified",
  "mechanism": "Datentabellen (UnitBalance/UnitWeapons/UpgradeData/MiscGame) + aktuelle Tech-Stufen + Heldenstufe",
  "latency": "Gepushter Snapshot + Schnellspur (Batch alle 5 s)",
  "signature": "stats(u, player: 'int | None' = None)",
  "doc": "Kampfwerte der Einheit, combat.UnitStats: max. TP/Mana, Rüstung (inkl. Angriffs-/Rüstungs-Upgrades, Beweglichkeit des Helden), Rüstungstyp, Laufgeschwindigkeit, Sichtweite bei Tag/Nacht,\nWaffen (was sie treffen können, Reichweite, Angriffsintervall, Schadensbereich, Angriffstyp, Flächenschaden). u als Einheit (nutzt automatisch Tech und Heldenstufe ihres Besitzers) oder als Vier-Zeichen-Code (player standardmäßig der eigene).\nDazu .dps_vs(gegner) / .hits_to_kill(gegner) / combat.time_to_kill(gruppe, gegner). ⚠ Ohne Gegenstände, Auren und Buffs."
 },
 {
  "name": "time_to_kill",
  "category": "observe",
  "status": "verified",
  "mechanism": "stats() + aktuelle TP",
  "latency": "Gepushter Snapshot",
  "signature": "time_to_kill(attackers, target) -> 'float | None'",
  "doc": "Wie viele Spielsekunden diese Einheiten gemeinsam brauchen, um target zu töten (mit den aktuellen TP von target; berücksichtigt Konter, Rüstung, Angriffs-/Rüstungs-Upgrades; nicht Stellungsspiel, Flächenschaden, Heilung).\nProfi-Nutzung: Beim Fokusfeuer zuerst das Ziel, das \"am schnellsten stirbt\" (kleinstes time_to_kill), nicht das nächstgelegene. Nicht angreifbar = None."
 },
 {
  "name": "time_of_day",
  "category": "observe",
  "status": "verified",
  "mechanism": "Erweiterungsbereich im Weltblock: GetFloatGameState(GAME_STATE_TIME_OF_DAY)",
  "latency": "Gepushter Snapshot",
  "signature": "time_of_day() -> 'float | None'",
  "doc": "Tageszeit im Spiel (Stunden, 0~24). Die Partie beginnt um 8 Uhr morgens; ein ganzer Tag = 480 Spielsekunden (je 240 Sekunden Tag und Nacht, skaliert mit der Tag-/Nacht-Geschwindigkeit).\nNicht lesbar (alte Runtime / nicht in einer Partie) = None."
 },
 {
  "name": "is_night",
  "category": "observe",
  "status": "verified",
  "mechanism": "Erweiterungsbereich im Weltblock (6~18 Uhr Tag)",
  "latency": "Gepushter Snapshot",
  "signature": "is_night() -> 'bool | None'",
  "doc": "Ob gerade Nacht ist (18:00~6:00). Profi-Spielweise: Nachts schlafen Creeps (beim Creepen triffst du zuerst, ohne umzingelt zu werden), alle Einheiten sehen weniger weit (gute Zeit für Überfälle),\nSchildwachen/Einheiten der Nachtelfen sind nachts neben Bäumen unsichtbar. Nicht lesbar = None."
 },
 {
  "name": "seconds_until",
  "category": "observe",
  "status": "verified",
  "mechanism": "Erweiterungsbereich im Weltblock + 480 Sekunden pro Tag (gemessen 20 Spielsekunden/Stunde)",
  "latency": "Gepushter Snapshot",
  "signature": "seconds_until(hour: 'float') -> 'float | None'",
  "doc": "Wie viele Spielsekunden es noch bis hour Uhr Spielzeit sind (z. B. seconds_until(18) = wie lange bis zum Einbruch der Nacht, zum Planen des nächtlichen Creepens)."
 },
 {
  "name": "items_on_ground",
  "category": "observe",
  "status": "verified",
  "mechanism": "Weltblock items[] (nur Gegenstände am Boden: Handle des Besitzers komplett FF)",
  "latency": "Gepushter Snapshot",
  "signature": "items_on_ground() -> 'list'",
  "doc": "Gegenstände am Boden [Item(addr, handle_lo, handle_hi, type, x, y, life)]. Aufheben/Verbrauchen löst das Event item.removed aus."
 },
 {
  "name": "trees",
  "category": "observe",
  "status": "verified",
  "mechanism": "Baumblock Local\\War3Trees_<pid> (alle 2 Sekunden aktualisiert)",
  "latency": "Gepushter Snapshot",
  "signature": "trees(x: 'float | None' = None, y: 'float | None' = None, limit: 'int' = 60) -> 'list'",
  "doc": "Lebende Bäume (in DestructableData mit tree in targType); mit (x,y) nach Entfernung sortiert, nah zuerst, höchstens limit Stück.\nJeder ist ein Tree(addr, handle_lo, handle_hi, type, x, y, life) und kann direkt an gather zum Holzfällen übergeben werden."
 },
 {
  "name": "events",
  "category": "observe",
  "status": "verified",
  "mechanism": "Event-Ring Local\\War3Events_<pid> (Veröffentlichungsvergleich + von der Runtime erfasste Schadens-Events)",
  "latency": "Gepushter Snapshot",
  "signature": "events() -> 'list'",
  "doc": "Was seit dem letzten Aufruf passiert ist: unit.appeared / unit.died / unit.removed / unit.damaged / order.changed /\nhero.levelup / owner.changed / item.appeared / item.removed / game.started (aus dem Vergleich der Veröffentlichungen, Genauigkeit = Veröffentlichungsperiode 50 ms),\ndazu damage / killed auf Engine-Ebene (die Runtime zeichnet sie sofort im Spiel-Thread auf, daher gibt es einen für **jeden einzelnen Treffer**):\n    damage: handle = der Getroffene, .source_addr = der Angreifer (mit snapshot().unit_by_addr zur Einheit), .value = tatsächlicher TP-Verlust,\n            .raw_damage = Schaden vor Rüstung, .attack_type (normal/pierce/siege/magic/chaos/hero/spell), .damage_type\n    killed: dieser Treffer hat die Einheit getötet, .source_addr = Killer\ndazu production.done, abgeleitet aus der von der Runtime verfolgten Produktionstabelle (Genauigkeit = Veröffentlichungsperiode): Einheit = Gebäude, .done_code = Vier-Zeichen-Code des Fertiggestellten,\n    .done_kind = 'training' (Einheit/Held/Wiederbelebung) / 'research' / 'construction' (Gebäude fertig) / 'upgrade' (Tier-up/Turm-Upgrade), .value = benötigte Spielsekunden\nSeit 09-25 ergänzt:\n    spell.cast: Einheit = Zaubernder, .spell Vier-Zeichen-Code der Fähigkeit, b Stufe, value Abklingzeit in Sekunden, x,y Zielpunkt (erkannt, wenn die Abklingzeit startet, Genauigkeit = Veröffentlichungsperiode)\n    player.left: .player = Nummer des Spielers, der gegangen ist / als besiegt entfernt wurde; game.ended: Partie verlassen\n    selection.changed: Die Auswahl des lokalen Spielers hat sich geändert (Einheiten mit g.selection() holen)\n    message: eine Zeile in einem Nachrichtenfeld auf dem Bildschirm (Spielhinweis, Chat, System): .text voller Text, .frame Nummer des Nachrichtenfelds,\n             .chat = {'channel', 'sender', 'text'} (bei Chat-Nachrichten; was der Spieler ins Chatfeld tippt, liest du hier)\n    ui.click / ui.hover / hotkey / mouse.world: Oberfläche & Eingabe (g.ui), .key ist der Canvas-key / die Hotkey-Schreibweise\nJedes ist ein Event(seq, kind, clock, addr, handle, type, owner, a, b, x, y, value, extra).\nIm Fair-Modus (fair=True) nur: Events eigener Einheiten, Events von Einheiten, die gerade sichtbar sind (oder vor bis zu 1 Sekunde noch sichtbar waren), Schaden gegen uns/durch uns,\nsowie die lokalen Oberflächen-, Nachrichten- und Partie-Events."
 },
 {
  "name": "selection",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Weltblock, Erweiterungsbereich selAddrs (die Runtime liefert bei jeder Veröffentlichung die Auswahl des lokalen Spielers mit)",
  "latency": "Gepushter Snapshot",
  "signature": "selection() -> 'list'",
  "doc": "Die Einheiten, die der lokale Spieler gerade ausgewählt hat (Haupteinheit zuerst; höchstens 12). Ändert sich die Auswahl, kommt ein selection.changed-Event."
 },
 {
  "name": "messages",
  "category": "observe",
  "status": "verified",
  "mechanism": "Shared Memory Local\\War3Msgs_<pid> (von der Runtime erfasste Bildschirmnachrichten)",
  "latency": "Gepushter Snapshot",
  "signature": "messages() -> 'list'",
  "doc": "Neue Nachrichten in den Nachrichtenfeldern auf dem Bildschirm seit dem letzten Aufruf: [{'text', 'frame', 'repeat', 'seq', 'game_ms'}].\nSpielhinweise (etwa, dass mehr Farmen nötig sind oder dass dort nicht gebaut werden kann), Chat und Systemnachrichten stehen alle hier; frame unterscheidet, welches Nachrichtenfeld.\nDieselben Nachrichten wie die message-Events im Event-Stream (jeweils mit eigenem Cursor)."
 },
 {
  "name": "ui",
  "category": "control",
  "status": "verified",
  "mechanism": "W3P 74 input_enable + Shared Memory Local\\War3Input_<pid> (die Runtime empfängt die Fenstereingaben)",
  "latency": "Shared Memory",
  "signature": "ui()",
  "doc": "Oberfläche & Eingabe (openwar3.ui.UI): klickbare Buttons und Auswahlkarten, Hotkeys, Position per Klick auf den Boden wählen, lesen, worauf die Maus zeigt.\nDer Klick auf einen Button kommt beim Spiel nicht an; rein lokale Eingabe + lokales Zeichnen, auch im Multiplayer sicher."
 },
 {
  "name": "tech",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Abfrage q_tech (Tech-Zähler des Spielers in der Engine)",
  "latency": "Schnellspur",
  "signature": "tech(code: 'str', player: 'int | None' = None) -> 'int | None'",
  "doc": "Forschungsstufe / Anzahl fertiger Gebäude (Upgrade-Ketten zählen mit: ein Schloss zählt auch als htow). player standardmäßig der eigene, jeder Spieler ist abfragbar."
 },
 {
  "name": "can_do",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Abfrage q_feasible (Machbarkeitsprüfung der Engine)",
  "latency": "Schnellspur",
  "signature": "can_do(u, code: 'str') -> 'int | None'",
  "doc": "Machbarkeitsurteil der Engine: 0/220 möglich; 3 Nahrung, 8 Gold fehlt, 9 Holz fehlt, 32 Warteschlange voll, 183 Voraussetzung fehlt, 185 Altar belebt gerade wieder, 221 nicht vorhanden/im Bau.\n⚠ Für Arbeiter, die ein Gebäude bauen, immer 221 – taugt nicht zur Prüfung des Bauplatzes (dafür build_near)."
 },
 {
  "name": "can_do_many",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Abfrage q_feasible × N, als ein Batch eingereicht",
  "latency": "Schnellspur × 1",
  "signature": "can_do_many(pairs) -> 'list'",
  "doc": "Viele can_do auf einmal: pairs = [(Einheit, Vier-Zeichen-Code), ...], liefert die Urteilscodes in derselben Reihenfolge (nicht abfragbar = None).\nWenn du planst, was in einem Tick gebaut/trainiert werden soll, frag zuerst alles gesammelt ab – N-mal schneller als einzelne can_do (Referenz-Brain 09-23: Bauplanung 76 -> 25 ms)."
 },
 {
  "name": "tech_many",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Abfrage q_tech × N, als ein Batch eingereicht",
  "latency": "Schnellspur × 1",
  "signature": "tech_many(codes, player: 'int | None' = None) -> 'dict'",
  "doc": "Viele Tech-/Gebäudezähler auf einmal: {Vier-Zeichen-Code: Anzahl oder None}."
 },
 {
  "name": "visible",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Abfrage q_visible (sichtbar / Kriegsnebel / schwarze Maske)",
  "latency": "Schnellspur",
  "signature": "visible(x: 'float', y: 'float') -> 'bool | None'",
  "doc": "Ob wir diesen Punkt gerade sehen (nicht im Kriegsnebel/in der schwarzen Maske). Ein Bot im Fair-Modus sollte nur sichtbare Gegner verwenden."
 },
 {
  "name": "gold_left",
  "category": "observe",
  "status": "inferred",
  "mechanism": "W3P-Abfrage q_mine_gold (Restgold der Mine in der Engine)",
  "latency": "Schnellspur",
  "signature": "gold_left(mine) -> 'int | None'",
  "doc": "Wie viel Gold noch in der Goldmine ist."
 },
 {
  "name": "enemy_ai_plan",
  "category": "observe",
  "status": "verified",
  "mechanism": "W3P-Abfrage q_captain (Computer-Captain, dem die gegnerische Einheit folgt)",
  "latency": "Schnellspur",
  "signature": "enemy_ai_plan(enemy_unit) -> 'dict | None'",
  "doc": "Captain der Computer-KI: wohin er seine Truppen führt (schon vor dem Abmarsch weißt du, wo er dich angreifen will). Nur gegen Computergegner; folgt die Einheit keinem Captain, None."
 },
 {
  "name": "batch",
  "category": "command",
  "status": "verified",
  "mechanism": "Befehle im Block werden zu einem Batch gesammelt und am Ende des Blocks auf einmal eingereicht (im selben Frame ausgeführt, nur einmal auf den Spiel-Thread warten)",
  "latency": "Schnellspur × 1",
  "signature": "batch() -> 'Batch'",
  "doc": "Fasst die Befehle eines Ticks zu einem Batch zusammen:\n\n    with g.batch() as b:\n        g.attack(archers, target)          # liefert Pending, wird erst nach dem Block zur Quittung\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\nJeder einzeln gesendete Befehl wartet einmal auf den Spiel-Thread (ca. 10 ms); ein Batch wartet nur einmal – damit kam das Referenz-Brain am 09-23 pro Runde von 48 -> 26 ms.\n* Die Claim-Prüfung läuft weiterhin pro Befehl (gehaltene Einheiten bekommen sofort eine held-Quittung und kommen nicht in den Batch);\n* Befehle im Block liefern Pending: .ok vor dem Ende des Blocks zu lesen löst eine Exception aus (die Quittung existiert noch nicht), danach wie eine Receipt verwenden;\n* Exception im Block = der ganze Batch verfällt (status 97 cancelled), gehaltene Einheiten werden zurückgegeben;\n* Abfragen (can_do / tech / visible …) sowie build_near und buy kommen nicht in den Batch, sondern werden wie bisher sofort gestellt – ihre Ergebnisse werden sofort gebraucht;\n  viele auf einmal abfragen mit can_do_many / tech_many;\n* verschachtelte with g.batch() gehen im äußersten Batch auf; über 16 Befehle teilt die Runtime automatisch in mehrere Abschnitte (je Abschnitt ein Warten)."
 },
 {
  "name": "order_of",
  "category": "observe",
  "status": "verified",
  "mechanism": "Order aus dem Snapshot + gerade angenommene Befehle dieses Prozesses (Quittungen)",
  "latency": "Gepushter Snapshot",
  "signature": "order_of(u) -> 'int | None'",
  "doc": "Aktuelle Order der Einheit, **inklusive der Befehle, die du in diesem Tick gerade erteilt hast** (solange der Snapshot noch nicht aufgeholt hat, gilt die neue Order aus der Quittung).\n⚠ 09-23 im Live-Spiel: hello_bot hatte gerade einen Bauern zum Bau eines Bauernhofs geschickt, im selben Tick sah rush_bot ihn im Snapshot als \"untätig\" und schickte ihn zur Kaserne – der Bauernhof blieb immer wieder halb fertig liegen.\n  Wenn du \"untätige/nicht bauende\" Einheiten auswählst, nimm diese Funktion statt u.order."
 },
 {
  "name": "move",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P point: move (extra-Bit = Art des Anstellens)",
  "latency": "Schnellspur",
  "signature": "move(units, x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Nach (x,y) gehen, unterwegs nicht angreifen (für Rückzüge). Nimmt eine Einheit oder eine Liste (Befehl im selben Frame).\nqueue='after': erst die aktuelle Aufgabe erledigen, dann gehen (hinter die aktuelle Order eingereiht). Quittung values[0] = wie viele Orders die Einheit nach dem Befehl in der Warteschlange hat (inkl. der aktuellen)."
 },
 {
  "name": "attack_move",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P point: attack auf Punkt",
  "latency": "Schnellspur",
  "signature": "attack_move(units, x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Angriffsbewegung (A auf den Boden): unterwegs angreifen, was auftaucht. queue wie bei move."
 },
 {
  "name": "attack",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: Zielbefehl (Rechtsklick smart)",
  "latency": "Schnellspur",
  "signature": "attack(units, target, force: 'bool' = False, queue: 'str | None' = None)",
  "doc": "target angreifen. Standardmäßig per Rechtsklick (auf Gegner = genau diesen angreifen; am 09-23 gemessen: Order-Ziel und Aufgabenziel sind beide diese Einheit).\n⚠ Das Ziel muss in Sicht sein, Unsichtbares wird abgelehnt (Grundcode 1001).\nforce=True nutzt den Angriffsbefehl 0x0F (nötig für eigene Einheiten/neutrale Kleintiere) – gemessen: Er setzt nur die Angriffs-Order und merkt sich das Ziel nicht,\ndie Einheit greift dann andere Gegner in der Nähe an. Für ein bestimmtes Ziel nicht verwenden."
 },
 {
  "name": "stop",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: stop",
  "latency": "Schnellspur",
  "signature": "stop(units)",
  "doc": "Alles anhalten (Order-ID 0x000D0004), auch eingereihte Orders werden gelöscht."
 },
 {
  "name": "hold",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: holdposition",
  "latency": "Schnellspur",
  "signature": "hold(units, queue: 'str | None' = None)",
  "doc": "Position halten (nicht hinterherlaufen, nur Ziele in Reichweite angreifen)."
 },
 {
  "name": "patrol",
  "category": "command",
  "status": "inferred",
  "mechanism": "W3P point: patrol",
  "latency": "Schnellspur",
  "signature": "patrol(units, x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Zwischen aktueller Position und (x,y) patrouillieren."
 },
 {
  "name": "attack_ground",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P point: attackground (Belagerungseinheiten / Mörsertrupp / Katapult)",
  "latency": "Schnellspur",
  "signature": "attack_ground(units, x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Boden angreifen: Artillerie feuert auf eine Fläche (unsichtbare Einheiten und Gegner hinter Bäumen treffen, einen Engpass sperren). Nur Einheiten, die den Boden angreifen können, nehmen den Befehl an."
 },
 {
  "name": "cancel",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: cancel",
  "latency": "Schnellspur",
  "signature": "cancel(building)",
  "doc": "Abbrechen: letzter Platz der Trainings-/Forschungswarteschlange (Geld zurück), Gebäude im Bau (75 % zurück), Hauptgebäude im Upgrade."
 },
 {
  "name": "path",
  "category": "command",
  "status": "verified",
  "mechanism": "Ein Batch: der erste Abschnitt wird sofort ausgeführt, der Rest in umgekehrter Reihenfolge mit queue='after' eingefügt (die Engine kann nur hinter der aktuellen Order einfügen)",
  "latency": "Schnellspur × 1",
  "signature": "path(units, points, attack: 'bool' = False)",
  "doc": "Eine Reihe von Punkten nacheinander abgehen (Shift-Wegpunkte: Wegpunkte, Türme umgehen, Aufklärungsrouten). attack=True: jeder Abschnitt ist eine Angriffsbewegung.\nEinmal eingereicht; eine Quittung pro Punkt (in der Reihenfolge von points)."
 },
 {
  "name": "gather",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: harvest (Goldmine oder Baum)",
  "latency": "Schnellspur",
  "signature": "gather(workers, target, queue: 'str | None' = None)",
  "doc": "Gold sammeln/Holz fällen (target ist eine Goldmine oder ein Baum aus trees()). ⚠ Nur untätigen Arbeitern zuweisen (idle_workers): Gibst du einem Arbeiter mit Aufgabe den Befehl erneut, unterbricht das den Sammelzyklus.\nProfi-Nutzung: nach dem Bauen zurück zur Mine = nach build(...) gather(worker, mine, queue='after')."
 },
 {
  "name": "repair",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: repair",
  "latency": "Schnellspur",
  "signature": "repair(workers, building, queue: 'str | None' = None)",
  "doc": "Reparieren / beim Bau helfen (Baustellen der Menschen und Orcs stehen still, wenn niemand daran baut)."
 },
 {
  "name": "build",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P build: Bau-Order, im selben Frame wird die Order des Arbeiters zur Bestätigung zurückgelesen",
  "latency": "Schnellspur",
  "signature": "build(worker, code: 'str', x: 'float', y: 'float', queue: 'str | None' = None)",
  "doc": "Lässt den Arbeiter code bei (x,y) bauen (Koordinaten auf 32 ausgerichtet). Quittung angenommen = die Order des Arbeiters ist bereits dieses Gebäude (oder der Baubeginn-Befehl);\nmit queue='after' = in die Order-Warteschlange des Arbeiters eingereiht (Quittung values[0] = Anzahl in der Warteschlange).\n⚠ Angenommen ≠ baubar: Auch einen Punkt im Wald nimmt die Engine sofort an, der Arbeiter scheitert erst, wenn er dort ankommt (am 09-23 gemessen); wird das Geld anderswo ausgegeben, erscheint die Baustelle ebenfalls nicht.\nWeißt du nicht, wo Platz ist, nimm build_near (verfolgt das Ergebnis, sperrt gescheiterte Punkte). Mehrere hintereinander: build_queue."
 },
 {
  "name": "build_queue",
  "category": "command",
  "status": "verified",
  "mechanism": "Ein Batch: das erste Gebäude sofort, der Rest in umgekehrter Reihenfolge mit queue='after'",
  "latency": "Schnellspur × 1",
  "signature": "build_queue(worker, plan)",
  "doc": "Ein Arbeiter baut mehrere Gebäude nacheinander (Shift-Bauen): plan = [(Vier-Zeichen-Code, x, y), ...]. Einmal eingereicht; Quittungen in der Reihenfolge von plan.\n⚠ Das Geld wird erst bei Baubeginn abgezogen (nicht beim Einreihen) – sind 3 Gebäude eingereiht, reicht das Geld aber nur für 1, scheitern die beiden anderen, wenn der Arbeiter dort ankommt."
 },
 {
  "name": "build_near",
  "category": "command",
  "status": "verified",
  "mechanism": "build Punkt für Punkt + Verfolgung (Baustelle erscheint = Erfolg; Arbeiter gibt die Order auf und es gibt keine Baustelle = dieser Punkt wird gesperrt)",
  "latency": "Schnellspur × Anzahl getesteter Punkte",
  "signature": "build_near(worker, code: 'str', x: 'float', y: 'float', min_r: 'float' = 450, max_r: 'float' = 1500, max_tries: 'int' = 24)",
  "doc": "Sucht um (x,y) von nah nach fern einen freien Platz und baut dort code. **Blockiert nicht**, kann in jedem Tick aufgerufen werden:\n  * Für diesen Gebäudetyp läuft noch ein Versuch (Arbeiter unterwegs) -> liefert diesen Punkt, ohne den Befehl zu wiederholen;\n  * der letzte Versuch hat geklappt (Baustelle erschienen) -> sucht bei Bedarf einen neuen Punkt;\n  * der letzte Versuch ist gescheitert (Arbeiter merkt erst vor Ort, dass kein Platz ist, die Engine zieht die Order zurück, keine Baustelle) -> dieser Punkt wird 45 Sekunden gesperrt, weiter mit dem nächsten;\n  * nicht genug Geld -> liefert direkt None (kein Versuch, keine Sperre); alle Punkte ausprobiert -> None.\n⚠ Warum verfolgen: am 09-23 im Live-Spiel nahm die Engine Punkte im Wald **sofort an**, der Arbeiter scheiterte erst vor Ort (die Quittung im selben Frame kann das nicht erkennen);\n  und die Bauplatzprüfung der Engine liefert für Arbeiter, die ein Gebäude bauen, immer 221, man kann also nicht erst \"prüfen\" und dann bauen. Nur offensichtlich belegte Punkte (mitten im Hauptgebäude) werden sofort abgelehnt."
 },
 {
  "name": "can_afford",
  "category": "observe",
  "status": "verified",
  "mechanism": "Eigene Ressourcen aus dem Push-Snapshot + Preise aus units.json",
  "latency": "Gepushter Snapshot",
  "signature": "can_afford(code: 'str') -> 'bool'",
  "doc": "Ob das aktuelle Gold/Holz für code reicht (Einheiten, Gebäude; nach den Preisen in units.json). Was nicht in der Preistabelle steht, gilt immer als bezahlbar.\n⚠ Die Vier-Zeichen-Codes für Tier-ups haben in der Tabelle kumulierte Preise, hier fällt das Ergebnis also eher vorsichtig aus; maßgeblich ist letztlich die Quittung der Engine."
 },
 {
  "name": "train",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: Vier-Zeichen-Code, bei Ablehnung mit Grundcode der Machbarkeitsprüfung",
  "latency": "Schnellspur",
  "signature": "train(building, code: 'str')",
  "doc": "Einheit trainieren / Technologie erforschen / Hauptgebäude aufwerten (Tier-up = dem Hauptgebäude selbst den Vier-Zeichen-Code des Zielgebäudes geben, z. B. 'hkee').\nBei Ablehnung sagt reason in der Quittung, warum (nicht genug Nahrung, Gold fehlt, Holz fehlt, Warteschlange voll, Voraussetzung fehlt …)."
 },
 {
  "name": "learn",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P learn: gilt erst als gelernt, wenn die Fertigkeitspunkte sinken",
  "latency": "Schnellspur",
  "signature": "learn(hero, ability: 'str')",
  "doc": "Held lernt eine Fähigkeit (Vier-Zeichen-Code, z. B. 'AHbz' Blizzard)."
 },
 {
  "name": "cast",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target / point / immediate (je nach Parametern)",
  "latency": "Schnellspur",
  "signature": "cast(u, spell, target=None, x: 'float | None' = None, y: 'float | None' = None)",
  "doc": "Zauber wirken. spell ist ein Order-String ('thunderbolt' Sturmblitz, 'blizzard', 'holybolt' Heiliges Licht …, siehe data/order-ids.txt) oder eine Order-ID.\nMit target = auf eine Einheit; mit x,y = auf den Boden; ohne beides = ohne Ziel (Donnerknall, Gottesschild, Wasserelementar beschwören).\nEine angenommene Quittung heißt nur, dass die Engine akzeptiert hat; ob der Zauber gewirkt wurde, zeigt cooldown() (Abklingzeit läuft) bzw. buffs() (Buff erscheint)."
 },
 {
  "name": "rally",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P rally",
  "latency": "Schnellspur",
  "signature": "rally(building, x: 'float | None' = None, y: 'float | None' = None, target=None)",
  "doc": "Sammelpunkt setzen (auf einen Punkt oder auf eine Einheit/Goldmine)."
 },
 {
  "name": "revive",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P revive: Liste toter Helden -> der Altar wirkt Wiederbeleben auf den toten Helden",
  "latency": "Schnellspur",
  "signature": "revive(altar, hero=None)",
  "doc": "Toten Helden am Altar wiederbeleben (ohne hero wird der erste der Liste wiederbelebt).\nHäufige Ablehnungsgründe (stehen in reason der Quittung): nicht genug Nahrung (auch Helden kosten Nahrung), nicht genug Geld, gerade erst gestorben (erst etwa 3 Spielsekunden nach dem Tod möglich),\nWiederbelebung läuft bereits (beim Annehmen räumt die Engine diesen Slot sofort)."
 },
 {
  "name": "pick_up",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: Rechtsklick auf Gegenstand",
  "latency": "Schnellspur",
  "signature": "pick_up(hero, item)",
  "doc": "Held hebt einen Gegenstand vom Boden auf (item aus items_on_ground). Danach erscheint er im Inventar, und am Boden wird das Event item.removed ausgelöst."
 },
 {
  "name": "use_item",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P use_item (nach Platznummer)",
  "latency": "Schnellspur",
  "signature": "use_item(hero, slot: 'int', target=None, x: 'float | None' = None, y: 'float | None' = None)",
  "doc": "Gegenstand in Inventarplatz slot (0~5) benutzen; optional mit Zieleinheit oder Zielpunkt.\n⚠ Bei Gegenständen auf einen Punkt (z. B. Elfenbeinturm) liefert die Engine auch bei Erfolg 0, die Quittung gilt immer als angenommen – prüf, ob der Inventarplatz leer geworden ist."
 },
 {
  "name": "drop_item",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P item_drop (nach JASS UnitDropItemPoint: dropitem 0xD0021 auf Punkt + Gegenstand als unmittelbares Ziel)",
  "latency": "Schnellspur",
  "signature": "drop_item(hero, slot: 'int', x: 'float', y: 'float')",
  "doc": "Den Gegenstand aus Inventarplatz slot bei (x,y) ablegen (der Held geht hin und legt ihn ab)."
 },
 {
  "name": "give_item",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P item_drop (nach JASS UnitDropItemTarget: dropitem auf Einheit)",
  "latency": "Schnellspur",
  "signature": "give_item(hero, slot: 'int', to)",
  "doc": "Den Gegenstand aus Inventarplatz slot an to übergeben (anderer Held / Einheit; der Held geht hin und übergibt ihn). An einen Laden = verkaufen (siehe sell_item)."
 },
 {
  "name": "sell_item",
  "category": "command",
  "status": "verified",
  "mechanism": "Wie give_item, Ziel ist ein Laden (gemessen: Staff of Sanctuary für 125 Gold verkauft)",
  "latency": "Schnellspur",
  "signature": "sell_item(hero, slot: 'int', shop)",
  "doc": "Den Gegenstand aus Inventarplatz slot an einen Laden verkaufen (der Held muss neben dem Laden stehen; nur verkaufbare Gegenstände werden angenommen, zum halben Preis)."
 },
 {
  "name": "move_item",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P target: Order 0xD0022+Platznummer, Ziel = Gegenstand (nach JASS UnitDropItemSlot)",
  "latency": "Schnellspur",
  "signature": "move_item(hero, slot: 'int', to_slot: 'int')",
  "doc": "Inventarplatz wechseln (Platz slot auf Platz to_slot verschieben; sind beide belegt, werden sie getauscht). Zum Sortieren der Hotkey-Belegung."
 },
 {
  "name": "buy",
  "category": "command",
  "status": "inferred",
  "mechanism": "W3P buy: der Laden verkauft an einen Helden daneben",
  "latency": "Schnellspur",
  "signature": "buy(shop, item_code: 'str')",
  "doc": "Gegenstand im Laden kaufen (für den Helden, der neben dem Laden steht). Fehlt eine Tech-Voraussetzung, liefert die Engine 0 und zieht kein Geld ab."
 },
 {
  "name": "call_to_arms",
  "category": "command",
  "status": "verified",
  "mechanism": "W3P immediate: townbellon/off",
  "latency": "Schnellspur",
  "signature": "call_to_arms(hall, on: 'bool' = True)",
  "doc": "Zu den Waffen (Menschen): Bauern werden zur Miliz (das Rathaus auf Stufe 1 hat diese Fähigkeit nicht, nur Bergfried/Schloss)."
 },
 {
  "name": "set_speed",
  "category": "control",
  "status": "verified",
  "mechanism": "Aktion 47 (25~800%)",
  "latency": "Steuerkanal",
  "signature": "set_speed(percent: 'int') -> 'bool'",
  "doc": "Spielgeschwindigkeit (100 = normale Geschwindigkeit)."
 },
 {
  "name": "pause",
  "category": "control",
  "status": "verified",
  "mechanism": "W3P pause",
  "latency": "Schnellspur",
  "signature": "pause(on: 'bool' = True)",
  "doc": "Spiel pausieren / fortsetzen. In der Pause steht die Engine-Uhr still, über die Schnellspur lassen sich weiterhin Befehle erteilen (der Event-Dispatch läuft weiter)."
 },
 {
  "name": "set_publish_period",
  "category": "control",
  "status": "verified",
  "mechanism": "Weltblock requestedPeriodMs",
  "latency": "Gepushter Snapshot",
  "signature": "set_publish_period(ms: 'int') -> 'None'",
  "doc": "Veröffentlichungsperiode des Weltzustands (16~1000 Millisekunden, Standard 50). Eine Erfassung kostet etwa 0.5 ms, auch 33 ms sind kein Problem; der Wert gilt für den ganzen Rechner, wer zuletzt schreibt, gewinnt."
 },
 {
  "name": "say",
  "category": "control",
  "status": "verified",
  "mechanism": "Aktion 56",
  "latency": "Steuerkanal",
  "signature": "say(u, text: 'str', seconds: 'float' = 4.0) -> 'bool'",
  "doc": "Über der Einheit erscheint eine Chat-Sprechblase (für Streams/Debugging, beeinflusst das Spiel nicht). Gibt False zurück, wenn keine Sprechblase erschienen ist; der Grund steht in g.last_say_error."
 },
 {
  "name": "message",
  "category": "control",
  "status": "inferred",
  "mechanism": "Aktion 45",
  "latency": "Steuerkanal",
  "signature": "message(text: 'str') -> 'bool'",
  "doc": "Schreibt eine Zeile in den Nachrichtenbereich unten links im Spiel (nur lokal sichtbar). Funktioniert erst, nachdem das Spiel selbst einmal einen Hinweis angezeigt hat (die DLL greift das Nachrichtenfeld bei dieser Gelegenheit ab)."
 },
 {
  "name": "end_game",
  "category": "control",
  "status": "verified",
  "mechanism": "Aktion 22",
  "latency": "Steuerkanal",
  "signature": "end_game() -> 'bool'",
  "doc": "Beendet diesen Spielprozess (farm.py --keep startet anhand von next_game.json automatisch die nächste Partie)."
 },
 {
  "name": "canvas",
  "category": "control",
  "status": "verified",
  "mechanism": "W3P 73 canvas_enable + Shared Memory Local\\War3Canvas_<pid> (die Runtime zeichnet in jedem Frame, bevor das Spiel den Mauszeiger zeichnet; der Mauszeiger liegt darüber)",
  "latency": "Shared Memory",
  "signature": "canvas()",
  "doc": "Canvas: zeichnet Textfelder, Panels, Fortschrittsbalken, Bilder sowie Kreise und Routen auf dem Boden ins Spielbild (openwar3.canvas.Canvas).\nDie Runtime zeichnet selbst, legt keine Spiel-Handles an und ändert den Spielzustand nicht – auch im Multiplayer sicher; Stil frei wählbar (chinesische Schrift, abgerundete Ecken, Transparenz)."
 },
 {
  "name": "press_to_continue",
  "category": "control",
  "status": "verified",
  "mechanism": "PostMessage WM_KEYDOWN/UP Leertaste an das Spielfenster (ohne den Fokus zu stehlen)",
  "latency": "Fensternachricht",
  "signature": "press_to_continue() -> 'bool'",
  "doc": "Drückt auf dem Ladebildschirm „Beliebige Taste drücken, um fortzufahren“ einmal die Leertaste. Viele RPG-/Story-Karten starten nach dem Laden erst auf Tastendruck (09-24 mit WarChasers gemessen:\nohne Tastendruck bleibt das Spiel im Ladebildschirm, Spieluhr 0, die Schnellspur wird nicht geleert). openwar3.run drückt beim Warten auf den Spielbeginn selbst, manuell ist das meist nicht nötig."
 },
 {
  "name": "map_data",
  "category": "observe",
  "status": "verified",
  "mechanism": "Kartendatei (Pfad aus --map des Launchers): w3u/w3t/w3a + wts, bei geschützten Karten die TXT-Dateien in der Karte",
  "latency": "Datei lesen (beim ersten Mal ca. 0.1 s)",
  "signature": "map_data()",
  "doc": "Daten der gerade gespielten Karte (openwar3.mapdata.MapData): name_of('HC07') liefert Namen eigener Einheiten/Gegenstände/Fähigkeiten, dazu hero_names, tooltip.\nEinheiten auf RPG-Karten sind meist von der Karte selbst definiert und fehlen in der eingebauten Namenstabelle; wurde das Spiel nicht vom Launcher gestartet (Kartendatei nicht auffindbar), kommt None zurück."
 },
 {
  "name": "jass",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "W3P 70 jass (die Runtime sucht die Native per Name in der Native-Tabelle, 1291 Stück)",
  "latency": "Schnellspur",
  "signature": "jass()",
  "doc": "Beliebige JASS-Natives per Name aufrufen: g.jass.CreateUnit(g.jass.Player(1), \"Hpal\", x, y, 270.0).\nParameter I/R/B/S/H werden automatisch konvertiert (Einheiten-/Gegenstandsobjekte direkt übergeben); im Multiplayer sind nur lesende Natives erlaubt. Details in openwar3/jass.py und docs/COMPANION_ZH.md."
 },
 {
  "name": "player_slots",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS GetPlayerController / GetPlayerSlotState / IsPlayerAlly",
  "latency": "Schnellspur",
  "signature": "player_slots() -> 'list[dict]'",
  "doc": "Die 16 Spielerslots: controller (user = echter Spieler / computer / neutral …), state (empty / playing / left), human, me, ally (ob mit mir verbündet).\nAuf RPG-Karten damit einen freien Slot für den Begleiter finden oder prüfen, ob es ein Einzelspielerspiel ist."
 },
 {
  "name": "spawn",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS CreateUnit + W3P 72 Handle -> Einheit",
  "latency": "Schnellspur",
  "signature": "spawn(code: 'str', x: 'float', y: 'float', player: 'int | None' = None, facing: 'float' = 270.0)",
  "doc": "Erzeugt eine Einheit bei (x,y) (player standardmäßig der lokale Spieler) und liefert die Einheit aus dem Snapshot (nach der nächsten Weltveröffentlichung, ca. 50 ms); klappt es nicht, None.\nDie gelieferte Einheit hat zusätzlich das Attribut jass_handle. ⚠ Nur im Einzelspieler nutzbar (im Multiplayer Desync)."
 },
 {
  "name": "set_alliance",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS SetPlayerAlliance",
  "latency": "Schnellspur",
  "signature": "set_alliance(a: 'int', b: 'int', allied: 'bool' = True, vision: 'bool' = True, control: 'bool' = False, xp: 'bool' = False, both: 'bool' = True) -> 'None'",
  "doc": "Bündnis von Spieler a gegenüber b setzen: allied = greifen sich nicht an + rufen sich gegenseitig zu Hilfe; vision teilt die Sicht; control teilt die Einheitensteuerung (b kann die Einheiten von a befehligen);\nxp teilt Erfahrung. both=True setzt beide Richtungen (control nur a -> b)."
 },
 {
  "name": "set_player_name",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS SetPlayerName",
  "latency": "Schnellspur",
  "signature": "set_player_name(player: 'int', name: 'str') -> 'None'",
  "doc": "Ändert den Spielernamen (den in Punktetafel, Chat und Verbündeten-Panel angezeigten). Zum Benennen des Begleiters."
 },
 {
  "name": "show_text",
  "category": "sandbox",
  "status": "verified",
  "mechanism": "JASS DisplayTimedTextToPlayer",
  "latency": "Schnellspur",
  "signature": "show_text(text: 'str', seconds: 'float' = 6.0, player: 'int | None' = None) -> 'None'",
  "doc": "Zeigt unten links auf dem Bildschirm eine Textzeile an (wie sie Karten-Trigger verwenden), standardmäßig für den lokalen Spieler. Unterstützt |cffRRGGBB-Farbcodes."
 }
]