Alle Formatseiten ↓
Einheiten, Skripte, Konfiguration

COB / BOS – Bytecode für Einheitenskripte

Aus coreprime/kbot @ 0ab97bf · Auf GitHub bearbeiten

Jede Einheit in Total Annihilation führt ein Skript aus, das ihre Pieces animiert, Mündungsblitze der Waffen erzeugt, Soundeffekte abspielt und auf Spielereignisse reagiert. Die Quellsprache ist BOS („Basic Object Script“); der kompilierte Bytecode, den die Engine tatsächlich ausführt, ist COB („Cobble“ – Cavedogs Wortspiel für „compiled BOS“). Eine Datei pro Einheit, benannt nach dem ObjectName aus der FBI der Einheit.

Tip

Probier es selbst.

# Decompile a unit's bytecode back to readable BOS
kbot cob decompile scripts/ARMCOM.cob

# Disassemble to opcode-level assembly with control-flow arrows
kbot cob disassemble scripts/ARMCOM.cob -a

# Lint a whole directory for common script issues
kbot cob lint scripts/ -q

# Round-trip the entire scripts/ tree to verify byte-perfect compile
kbot cob roundtrip scripts/

Alle Flags (und die vollständige Tabelle der Lint-Regeln) stehen in der kbot cob-Referenz der CLI.

Aus Go heraus. Verwende formats/scripting aus kbot-io:

import "github.com/coreprime/kbot-io/formats/scripting"

cob, _ := scripting.LoadFromFile("scripts/ARMCOM.cob")
fmt.Printf("%d scripts, %d pieces\n", cob.NumScripts, cob.NumPieces)
for i, name := range cob.ScriptNames {
    fmt.Printf("  [%d] %s\n", i, name)
}

Zwei Sprachen, ein Dateiformat

Stufe Format Werkzeuge
Quelltext BOS – C-ähnlich, strukturiert Von Hand geschrieben; Switecks Scriptor; kbot cob decompile gewinnt es per Round-Trip aus COB zurück
Kompiliert COB – stackbasierter Bytecode Von der Engine geladen; erzeugt von Scriptor oder kbot cob compile

Die Engine sieht immer nur COB. BOS ist eine Erleichterung für menschliche Autoren; wenn du „das Skript einer Einheit bearbeitest“, bearbeitest du in Wahrheit den BOS-Quelltext, kompilierst ihn neu zu COB und lieferst das COB im Add-on-.ufo der Einheit aus.

Ein minimales Beispiel:

// armcom.bos — fragment from the Arm Commander
piece base, pelvis, lthigh, lleg;

#define SIG_WALK 2

Create() {
    hide pelvis;
    set-signal-mask SIG_WALK;
}

Walk() {
    set-signal-mask SIG_WALK;
    while (1) {
        turn lthigh to x-axis  [-25] speed [50];
        turn lleg   to x-axis  [50]  speed [100];
        wait-for-turn lthigh around x-axis;
        sleep 100;
    }
}

kbot cob compile übersetzt das in einen binären Blob. kbot cob disassemble -a zeigt den Opcode-Strom mit Kontrollfluss-Pfeilen daneben; kbot cob decompile geht den umgekehrten Weg und gewinnt BOS zurück, das fast immer bytegleich den Round-Trip übersteht (kbot cob roundtrip prüft das über den gesamten Spielkatalog).

Ein Quelltext wird als TA-Skript (COB-Version 4) kompiliert, sofern er nicht mit .version 6 beginnt. Der Compiler verweigert, was TA 3.1c falsch ausführen würde, statt einen Platzhalter zu schreiben: Die Konstrukte von TA: Kingdoms (play-sound, Mission-Command, __tak_math_*, .sound_name) und % sind in einem TA-Skript Fehler (TA hat keine Modulo-Instruktion, und 0x10037000, das Wort, zu dem % früher wurde, ist dort das bitweise XOR), ebenso unbekannte Bezeichner und Funktionen, die mehr als die 32 Stack-Slots des Spiels brauchen. Was sich trotzdem kompilieren lässt, aber einen Blick verdient – eine zweimal definierte Funktion (jeder Aufruf bindet an die erste Definition, so wie die Namenssuche des Spiels) –, wird als Warnung ausgegeben, zusammen mit jedem Befund der TA-Kompatibilitätsregeln des Linters; mit --strict lassen Warnungen den Befehl fehlschlagen. kbot cob assemble wendet dieselben Prüfungen auf handgeschriebene Listings an.


COB-Aufbau auf der Platte

Important

Zwei Dialekte. Dieser Abschnitt beschreibt COB v4, den Dialekt, den Total Annihilation ausliefert. TA: Kingdoms verwendet COB v6 (VersionSignature == 6), das alles Folgende beibehält, den Header aber um 8 Byte erweitert und hinter dem Array der Piece-Namen eine zusätzliche Offset-Tabelle für Befehls-Strings einfügt. Die Einzelheiten von v6 stehen unter TA: Kingdoms – Unterschiede von COB v6; die Opcode-Liste gilt für beide, die zusätzlichen Opcodes, die es nur in TAK gibt, sind in Anhang C dokumentiert.

typedef struct {                          // 44-byte header (11 × uint32)
    uint32 VersionSignature;              // 4 for TA, 6 for TA: Kingdoms
    uint32 NumScripts;                    // Number of functions (Create, Killed, …)
    uint32 NumPieces;                     // Number of named pieces
    uint32 LengthOfScripts;               // Total code size, in DWORDs
    uint32 NumberOfStaticVars;            // Static-variable count
    uint32 UKZero;                        // Always 0 in retail bytecode; purpose unknown
    uint32 OffsetToScriptCodeIndexArray;  // → uint32 × NumScripts: code-index per script
    uint32 OffsetToScriptNameOffsetArray; // → uint32 × NumScripts: absolute offsets into string pool
    uint32 OffsetToPieceNameOffsetArray;  // → uint32 × NumPieces: absolute offsets into string pool
    uint32 OffsetToScriptCode;            // → packed uint32 opcode stream
    uint32 OffsetToNameArray;             // Byte just past the piece-name offset array (= sound-name offset table start for TAK v6, or string-pool start for TA v4)
} COBHeader;

Note

Die kanonischen Feldnamen entsprechen dem Struct HEADER_COB von Scriptor. Frühere kbot-Versionen nannten LengthOfScripts / NumberOfStaticVars / UKZero / OffsetToNameArray „Unknown0/1/2/3“ – dieselben Offsets, nur jetzt mit den dokumentierten Bedeutungen.

Danach folgt, in diesem Aufbau:

┌─ Script code ─────────────────────────────┐  uint32 opcodes/operands, packed
├─ Script code-index table ─────────────────┤  uint32[NumScripts] — *not* byte offsets
├─ Script name offset table ────────────────┤  uint32[NumScripts]
├─ Piece name offset table ─────────────────┤  uint32[NumPieces]
└─ String pool ─────────────────────────────┘  NUL-terminated names, in order

Ein Skript auflösen

Die Code-Index-Tabelle enthält Wort-Offsets als uint32, keine Byte-Offsets. So findest du den Bytecode für Skript Nummer i:

uint32 first_instr_word = ScriptCodeIndexArray[i];
uint32 byte_offset      = OffsetToScriptCode + first_instr_word * 4;

Das Skript endet dort, wo das nächste beginnt (beim letzten Skript: am Anfang der Index-Tabelle).

Important

String-Offsets in den Namenstabellen sind absolute Datei-Offsets, nicht relativ zum String-Pool. Cavedogs Werkzeuge haben den String-Pool nie verschoben, deshalb spielte das in der Praxis keine Rolle; wenn du ein COB von Hand umorganisierst, muss jeder Offset angepasst werden.


Der Opcode-Strom

COB ist eine stackbasierte virtuelle Maschine. Die meisten Opcodes holen ihre Argumente vom Datenstack des jeweiligen Skripts, erledigen ihre Arbeit und legen ein Ergebnis zurück. Manche Opcodes haben Inline-Operanden (Sprungziele, Piece-Nummern).

Jeder Opcode ist ein uint32 mit diesem Aufbau:

       MSB                          LSB
  ┌─────────┬─────────┬─────────┬─────────┐
  │   0x10  │ family  │ op      │ flags   │
  └─────────┴─────────┴─────────┴─────────┘

Das höchste Byte ist immer 0x10; family/op/flags unterscheiden sich je Opcode. Das Spiel vergleicht nicht das ganze Wort: Es verzweigt anhand von word & 0x100FF000, Bits außerhalb dieser Maske werden also ignoriert, und 0x10064001 läuft als JUMP (kbots Disassembler schreibt ein solches Wort als JUMP@0x10064001, damit es unverändert zurückassembliert wird). PUSH (0x10021000) und POP (0x10023000) lesen dann ihre Quelle bzw. ihr Ziel aus den unteren drei Bits. Der Katalog ist scripting/opcodes.go in kbot-io; scripting.Opcodes() listet jede Instruktion mit ihren Inline-Operanden und ihrem Stack-Effekt auf.

Opcode-Familien

Familie Beispiele Anmerkungen
0x100x MOVE, TURN, SPIN, SHOW, HIDE, EMIT_SFX Piece-Animation. Piece-Nr. und Achsen-Nr. folgen inline.
0x101x WAIT_FOR_TURN, WAIT_FOR_MOVE, SLEEP Asynchrone Barrieren – das Skript gibt die Kontrolle ab, bis die Bedingung erfüllt ist.
0x102x PUSH_CONST, PUSH_LOCAL, PUSH_STATIC, POP_* Stack-Manipulation.
0x103x ADD, SUB, MUL, DIV, BITWISE_AND, BITWISE_OR, XOR, NOT Arithmetik und Bitoperationen. Es gibt kein Modulo.
0x104x RAND, GET_UNIT_VALUE, GET, IS_CARRYING_UNIT, CARRIER_UNIT_ID Abfragen an die Engine.
0x105x <, <=, >, >=, ==, !=, &&, ||, XOR_ALT, ! Vergleiche und logische Operationen. XOR_ALT ist ein bitweises XOR, kein logisches.
0x106x START_SCRIPT, CALL_SCRIPT, DISCARD_CALL, JUMP, RETURN, JUMP_IF_FALSE, SIGNAL, SET_SIGNAL_MASK Kontrollfluss und Nebenläufigkeit.
0x107x EXPLODE, PLAY_SOUND Spezialeffekte. PLAY_SOUND gibt es nur in TA: Kingdoms.
0x108x SET_VALUE, ATTACH_UNIT, DROP_UNIT Änderungen am Zustand der Engine.

Eine kleine, aber repräsentative Auswahl:

0x10021001 PUSH_CONST     <value>      ( -- v )
0x10021002 PUSH_LOCAL     <var#>       ( -- v )
0x10023002 POP_LOCAL      <var#>       ( v -- )
0x10033000 MUL                         ( a b -- a*b )
0x10051000 LESS_THAN                   ( a b -- a<b )
0x10066000 JUMP_IF_FALSE <byteOffset>  ( v -- )    pops; falls through if v != 0
0x10064000 JUMP          <byteOffset>  ( -- )
0x10065000 RETURN                      ( v -- )    pops the return value
0x10001000 MOVE          <piece> <axis> ( speed distance -- )
0x10003000 SPIN          <piece> <axis> ( speed -- )
0x1000F000 EMIT_SFX      <piece>       ( flags -- )
0x10068000 SET_SIGNAL_MASK              ( mask -- )

Note

Sprung-Offsets sind Byte-Offsets relativ zum Anfang des Skript-Code-Abschnitts (nicht zur aktuellen Instruktion). JUMP 0x4C bedeutet „springe nach OffsetToScriptCode + 0x4C“. Das Flag -a des Disassemblers zeichnet Pfeile zwischen zusammengehörigen Instruktionen, damit das sofort ins Auge fällt.


Nebenläufigkeitsmodell

Bei einer Einheit können viele Skripte gleichzeitig laufen – Walk, Aim, SmokeUnit, MoveWatcher usw. Sie werden kooperativ eingeplant:

  • sleep <ticks> gibt für N Ticks (je 1/30 s) die Kontrolle ab.
  • wait-for-turn <piece> around <axis> wartet, bis dieses Piece seine aktuelle turn-Animation beendet hat. Dasselbe gilt für wait-for-move.
  • signal <n> beendet jedes Skript, für das signal_mask & n != 0 gilt.
  • set-signal-mask <n> legt fest, an welchen Signalen das aktuelle Skript interessiert ist.

Über die Signalmaske setzen Skripte Präemption um: MoveWatcher setzt die Maske SIG_MOVE und beobachtet die Geschwindigkeit; hält die Einheit an, sendet es SIG_MOVE und beendet so die Laufanimationen sauber, bevor die Ruhepose beginnt.

Warning

SET_SIGNAL_MASK meldet nur Interesse an – es beendet nichts. Erst das Senden von signal X stoppt Skripte, bei denen die Maske X gesetzt ist. Die Lint-Regel signal-never-signalled in kbot cob lint findet Skripte, die eine Maske setzen, die nie jemand signalisiert (was fast immer ein Bug ist).

Wie Präemption tatsächlich abläuft

Ein gängiges Muster: MoveUnit startet ein Animationsskript Walk; endet die Bewegung, sendet die Engine SIG_MOVE, um sauber aufzuräumen, bevor Idle beginnt. Der Ablauf:

   Engine                 MoveUnit script        Walk script        Idle script
     │                          │                     │                  │
     │  unit ordered to move    │                     │                  │
     ├─ start-script MoveUnit──▶│                     │                  │
     │                          ├─ set-signal-mask    │                  │
     │                          │   SIG_MOVE          │                  │
     │                          ├─ start-script Walk─▶│                  │
     │                          │                     ├─ set-signal-mask │
     │                          │                     │   SIG_MOVE       │
     │                          │                     │ (loops:          │
     │                          │                     │   turn pieces,   │
     │                          │                     │   sleep, repeat) │
     │                          │                     │                  │
     │  movement completes      │                     │                  │
     ├─ engine sets velocity=0  │                     │                  │
     │                          ├─ signal SIG_MOVE ──┐│                  │
     │                          │                    ││                  │
     │   ◀─── KILLED ──────────────────────────────  ◀┤                  │
     │   ◀─── KILLED ──────────┤                     │                  │
     │                                                                   │
     ├─ start-script Idle ─────────────────────────────────────────────▶│
     │                                                                   │ (loops)

Drei Dinge fallen auf:

  1. Beide Skripte, die SIG_MOVE setzen, werden beendet, wenn das Signal ausgelöst wird – auch MoveUnit selbst (was egal ist; seine Arbeit ist getan).
  2. Das signalisierende Skript (MoveUnit) überlebt lange genug, um das Signal zu senden, bevor es beendet wird; die Laufzeitumgebung prüft die Masken erst nachdem der Opcode signal zurückgekehrt ist.
  3. Idle setzt SIG_MOVE nicht, das nächste signal SIG_MOVE lässt es also unberührt.

Beispiel im Detail – cortruck.cob (Core Construction Truck)

kbot cob decompile scripts/cortruck.cob:

piece base;

SmokeUnit() {
    var local_0, local_1, local_2;

    while (get BUILD_PERCENT_LEFT) { sleep 400; }

    while (1) {
        local_0 = get HEALTH;
        if (local_0 < 66) {
            local_2 = (256 | 2);
            if (rand(1, 66) < local_0) { local_2 = (256 | 1); }
            emit-sfx local_2 from base;
        }
        local_1 = (local_0 * 50);
        if (local_1 < 200) { local_1 = 200; }
        sleep local_1;
    }
    return 0;
}

Create() { return 0; }

Killed() {
    var severity;
    if (severity <= 25) { explode base type (32 | 256); return 0; }
    if (severity <= 50) { explode base type (32 | 256); return 0; }
    if (severity <= 99) { explode base type (32 | 256); return 0; }
    explode base type (32 | 256);
}

Die zugehörige Disassemblierung (kbot cob disassemble -a -s SmokeUnit) zeigt den tatsächlichen Opcode-Strom, mit JUMP_IF_FALSE/JUMP-Pfeilen, die sich durch die Rümpfe der while/if-Blöcke ziehen – nützlich, wenn die Ausgabe des Decompilers mehrdeutig ist.


Linting

kbot cob lint (und der Lint-Tab des Asset-Explorers) führt den Standard-Regelsatz von kbot-io aus: Stilregeln, die aus echten Bugs in Cavedogs eigenen Skripten und aus von der Community eingereichten Mod-Patches abgeleitet sind, zwei Prüfungen auf Dateiebene und – für jedes COB, das nicht Version 6 angibt – die Kompatibilitätsregeln für TA 3.1c. Der Stilkatalog:

Regel Schweregrad Findet
unused-piece Warnung Piece deklariert, aber in keinem Skript verwendet.
unused-static / unused-local Warnung Variable angelegt, aber nie gelesen.
always-true / dead-code Info / Warnung if(1) / while(0).
long-function / high-complexity Warnung > 100 Instruktionen oder zyklomatische Komplexität > 15.
invalid-call Fehler call-script auf eine nicht existierende Funktion.
speed-zero Warnung move/turn mit speed <0> – die Animation endet nie.
empty-function Info Der Funktionsrumpf besteht nur aus return 0.
duplicate-animation Warnung Zwei identische Animationsbefehle direkt hintereinander.
sleep-only-guard Info Ein if-Block enthält nichts außer sleep.
duplicate-if Info Zwei if mit derselben Bedingung hintereinander.
raw-signal Info signal 4 statt signal SIG_AIM.
unnamed-global Info global_3 statt einer benannten statischen Variable.
signal-never-signalled Warnung Ein Skript maskiert ein Signal, das niemand sendet.
recursive-call Warnung Zyklus aus call-script-Aufrufen.
duplicate-function Warnung Zwei Skripte teilen sich einen Namen; das Spiel ruft nur das erste auf.
malformed-cob Fehler / Warnung Die Datei lässt sich nicht laden, oder der Code eines Skripts ist abgeschnitten (Fehler); Namenstabellen, die der Leser toleriert hat (Warnung).

Die TA-Kompatibilitätsregeln melden, woran TA 3.1c abbricht oder was es anders ausführt. Sie sind Fehler, erscheinen wie die anderen in der SARIF-Ausgabe von --ci und überspringen Dateien von TA: Kingdoms (Version 6):

Regel Findet
ta-kingdoms-opcode PLAY_SOUND, MISSION_COMMAND oder einen TAK_MATH_*-Operator in einem TA-Skript. TA hat dafür keinen Handler, und das Skript bricht ab, sobald es einen davon erreicht.
ta-unknown-opcode Ein Opcode-Wort, das kein Spiel ausführt.
ta-push-flags Ein PUSH, dessen untere Bits nicht 1 (Konstante), 2 (lokal) oder 4 (statisch) sind, oder ein POP, dessen untere Bits nicht 2 oder 4 sind – einschließlich der flaglosen Wörter 0x10021000 und 0x10021008, die manche Werkzeuge geschrieben haben.
ta-stack-limit Mehr als 32 STACK_ALLOCs, oder lokale Variablen plus anstehende Werte über die 32 Stack-Slots des Spiels hinaus; das Spiel prüft das nicht und überschreibt den Speicher hinter dem Skript.
ta-get-arguments Ein GET, das mit weniger als fünf anstehenden Werten erreicht wird. GET holt immer einen Port und vier Argumente vom Stack, ein get mit nur einem Argument nimmt den Rest also aus den lokalen Variablen der Funktion und überschreibt sie.
ta-stack-underflow Jede andere Instruktion, die mehr Werte vom Stack holt, als anstehen, oder Pfade, die mit unterschiedlicher Stacktiefe zusammenlaufen.
ta-discard-call DISCARD_CALL mit mehr als vier Argumenten.

Die vollständigen Regelbeschreibungen stehen in der README von kbot. Der Decompiler behält die ursprüngliche Nummerierung von Variablen und Pieces absichtlich bei, damit ein Zyklus dekompilieren→linten→korrigieren→kompilieren die Mods sichtbar macht, die Pflege brauchen.


Stolperfallen

Warning

call-script ist 0x10062000. 0x10063000 ist eine andere Instruktion, DISCARD_CALL: Sie hat dieselben zwei Inline-Wörter (Skriptnummer und Argumentanzahl), holt aber nur die Argumente vom Stack und führt nichts aus. Kein Skript der Verkaufsversion verwendet sie; kbots Decompiler schreibt sie als __discard_call(word, args…).

Warning

Mehrere veröffentlichte Opcode-Tabellen benennen die Bit-Wörter falsch. TA führt 0x10037000 als bitweises XOR aus (nicht als Modulo – TA hat keine Modulo-Instruktion), 0x10038000 als unäres bitweises NOT (nicht als XOR) und 0x10059000 als zweites bitweises XOR (kein logisches XOR: 6 XOR_ALT 3 ergibt 5). 0x1003A000 ist ein Operator von TA: Kingdoms, an dem TA abbricht. Listings mit den alten Namen (MOD, BITWISE_XOR, BITWISE_NOT, LOGICAL_XOR) werden weiterhin zu genau den Wörtern assembliert, aus denen sie entstanden sind.

  • Alle Offsets im COB-Header sind absolute Datei-Offsets, die Einträge von ScriptCodeIndexArray dagegen Wort-Offsets innerhalb des Code-Abschnitts. Beides wird leicht verwechselt.
  • Piece-Nummern in Opcodes sind Indizes in die Tabelle der Piece-Namen, keine Strings. Das COB enthält die Namen als String-Pool; die Laufzeitumgebung verwendet der Geschwindigkeit wegen Indizes.
  • Die get UNIT_VALUE-Opcodes unterscheiden sich subtil zwischen TA und TA: Kingdoms. TA: Kingdoms hat Wert-IDs für Magie-Regeneration, Befehlspunkte usw. hinzugefügt; die Werttabelle von TA ist kompakter. kbots Decompiler benennt derzeit die TA-Menge; TAK-Portnummern ≥ 21 werden als numerische Literale ausgegeben (die trotzdem den Round-Trip durch den Assembler überstehen). Den vollständigen Unterschied beschreibt TA: Kingdoms – Unterschiede von COB v6.
  • Variablen gibt es in zwei Sorten: local_N (funktionslokaler Stack-Slot) und global_N (statische Variable pro Einheit, die über Aufrufe hinweg erhalten bleibt). Der Decompiler gibt standardmäßig local_N / global_N aus; benenne sie im Quelltext um, damit er lesbarer wird.
  • Das Cavedog-Scriptor-Tool setzt CURRENT_SPEED gelegentlich als Literal 29 ein, was Skripte im Stil von MoveWatcher nach einem Round-Trip kaputtmacht. kbots Decompiler stellt den symbolischen Namen wieder her; wenn du unbedingt einen Round-Trip durch Scriptor machen musst, setze Klammern: (get CURRENT_SPEED) > 5.

Typische Größen

Asset Beobachteter Bereich in Cavedogs scripts/*.cob
Dateigröße 0,5 KB – 25 KB
Header-Overhead immer 44 Byte
Skripte pro Einheit 3 – 30 (typische Einheiten: 5–10)
Pieces pro Einheit 3 – 80 (Krogoth ≈ 80)
Größe des Code-Abschnitts 300 B – 20 KB (schwankt stark mit der Komplexität)
armcom.cob (Commander) ~14 KB, 30 Skripte
cortruck.cob (einfache Einheit) ~750 B, 3 Skripte

TA: Kingdoms – Unterschiede von COB v6

Note

Dieser Abschnitt beschreibt nur, was in TA:K anders ist. Alles oben beschreibt COB v4 (TA). TA: Kingdoms behält dieselbe Stack-VM, denselben String-Pool, dasselbe Kodierungsschema der Opcodes und dieselbe Mechanik für Skript- und Piece-Namen bei; die Unterschiede sind ein erweiterter Header, eine zusätzliche String-Tabelle hinter dem Array der Piece-Namen und vier neue Opcodes.

Dateiaufbau von v6

┌─ 44-byte v4 header ──────────────────────────────────┐
│ VersionSignature == 6, NumScripts, NumPieces, …      │
├─ Sub-header (8 bytes, TAK-only) ─────────────────────┤  (soundNameOffsetTableStart, soundNameCount)
├─ Script code (uint32 opcode stream) ─────────────────┤  same encoding as v4
├─ ScriptCodeIndexArray (uint32 × NumScripts) ─────────┤  unchanged
├─ ScriptNameOffsetArray (uint32 × NumScripts) ────────┤  unchanged
├─ PieceNameOffsetArray (uint32 × NumPieces) ──────────┤  unchanged
├─ SoundNameOffsetArray (uint32 × Count) ──────────────┤  TAK-only — count from sub-header[1]
└─ String pool ────────────────────────────────────────┘  script names → piece names → sound names (in that order)

Der 8 Byte große Sub-Header liegt zwischen OffsetToNameArray (dem Ende des kanonischen Headers aus 11 DWORDs bei Byte 44) und dem Anfang des Code-Abschnitts (OffsetToScriptCode, typischerweise 0x34 = 52). Er enthält zwei uint32-Werte in Little-Endian:

Feld Bedeutung
[0] Absoluter Datei-Offset der Offset-Tabelle der Sound-Namen. Immer gleich OffsetToPieceNameOffsetArray + NumPieces × 4 – also das Byte direkt hinter dem Offset-Array der Piece-Namen. Redundant zum kanonischen Aufbau; der Writer rekonstruiert ihn.
[1] Anzahl der Sound-Namen in der Tabelle. Maßgebliche Anzahl.

Jeder Eintrag der Offset-Tabelle der Sound-Namen ist der absolute Datei-Offset eines NUL-terminierten Strings im String-Pool – etwa "SetMission o 1, s", "create ARASWORD", "ARROW10". Der Bytecode verweist über den Opcode MISSION_COMMAND per Index (nicht per Offset) auf sie (siehe Anhang C). Die Tabelle heißt Sound-Namen-Array, weil der kanonische Scriptor-Compiler Strings über die Aktion ADDSOUND darin registriert; tatsächlich enthält sie Befehls-Strings für die Engine und keine Audio-Dateinamen.

Die Darstellung in kbot

scripting.COB führt die Sound-Namen als strukturiertes []string-Feld – es werden keine undurchsichtigen Bytes mitgeschleppt. Alles andere (die Werte des Sub-Headers, die Offset-Tabelle, die Offsets der einzelnen Strings im Pool) wird beim Schreiben aus dieser Liste rekonstruiert:

type COB struct {
    // …canonical TA fields…
    SoundNames []string // TAK-only; nil for v4
}

Bei v4-Dateien ist der Slice immer nil, und sowohl der Sub-Header als auch die Offset-Tabelle der Sound-Namen fehlen in der Ausgabe.

Disassembler und Decompiler geben jeden String als Direktive .sound_name aus, damit die Strings einen Round-Trip durch .coba und .bos überstehen:

.version 6
.statics 6
.sound_name "create NPCEMEN"
.sound_name "SetMission o 1, s"
.sound_name "ARROW10"
.script Start
…

Neue Opcodes

TAK-.cob-Dateien verwenden Instruktionen, für die TA 3.1c keinen Handler hat (PLAY_SOUND, MISSION_COMMAND und die TAK_MATH_*-Operatoren), dazu DONT_SHADOW, das TA als No-op ausführt. Ihre genaue Semantik im Spiel ist nicht dokumentiert, aber ihr binärer Aufbau (Größe + Anzahl der Inline-Parameter) ist aus jeder .cob der TAK-Verkaufsversion bekannt, und das genügt, damit der Round-Trip disassemblieren→assemblieren die ursprünglichen Bytes unverändert wiederherstellt. Die vollständige Referenz steht in Anhang C.

Stand des Round-Trips

Pipeline TA TA: Kingdoms
disassemble → assemble (bytegleich) 278 / 278 ✅ 267 / 267 ✅
decompile → compile (bytegleich) 278 / 278 ✅ Einfache Einheitenskripte und viele Missions-COBs (≈ 70 / 267) überstehen den Round-Trip bytegenau. Die übrigen Fehlschläge sind Kontrollflussmuster, bei denen die strukturierte Rekonstruktion von if/while durch den Decompiler ein JUMP-Layout wählt, das der Compiler leicht anders erzeugt – der Bytecode ist semantisch gleichwertig (jeder TAK-Opcode, jeder Befehls-String, der Sub-Header und das Versionswort von v6 bleiben erhalten), nur eben nicht bytegleich. Dieselbe Art von Abweichung träfe jeden Zyklus dekompilieren→kompilieren bei einem nicht trivialen Skript.

BOS-Syntax für die TAK-Erweiterungen

Der Decompiler gibt diese Pseudoaufrufe aus, damit TAK-COBs einen Round-Trip durch BOS überstehen. Der Compiler akzeptiert dieselbe Syntax, aber nur nach .version 6 (mit Ausnahme von dont-shadow – TA führt es als No-op aus). dont-shadow und Mission-Command verwenden die kanonischen TAK-Schlüsselwörter von Scriptor; die Mathe-Opcodes haben in Scriptor keinen dokumentierten Namen (es bezeichnet sie als ?? und ????), daher behält kbot die Platzhalter __tak_math_* bei:

// Statement-level (emitted on their own line):
dont-shadow(<piece>);                                  // DONT_SHADOW — disables shadow casting for one piece
Mission-Command("sound name", arg1, arg2, …);          // MISSION_COMMAND — engine call (statement form drops the return value)
return_val = Mission-Command("sound name", args…);     // assignment form keeps the pushed result

// Expression-level (binary operators: two values popped, one pushed):
local_x = __tak_math_09(a, b);   // TAK_MATH_09
local_x = __tak_math_0a(a, b);   // TAK_MATH_0A
local_x = __tak_math_0b(a, b);   // TAK_MATH_0B

Das erste Argument von Mission-Command ist der Sound-Name als String in Anführungszeichen – der Compiler sucht ihn in der Sound-Namen-Tabelle (die zugehörige Direktive .sound_name muss also weiter oben in der Datei stehen). Die übrigen Argumente werden auf den Stack gelegt, und die Engine verbraucht sie.

Zwei Direktiven am Dateianfang tragen die restlichen Metadaten von v6:

.version 6
.sound_name "create NPCEMEN"
.sound_name "SetMission o 1, s"
.sound_name "ARROW10"

.version wird immer ausgegeben (damit v4-Dateien von TA dieselbe Pipeline identisch durchlaufen). Jedes .sound_name ist ein Eintrag in der Sound-Namen-Tabelle des jeweiligen COB; der Writer baut den 8-Byte-Sub-Header und die Offset-Tabelle der Sound-Namen aus len(SoundNames) und der Reihenfolge der Einträge neu auf.

Den TAK-Epilog STACK_ALLOC ; RETURN, den jedes wertlose return; in v6 erzeugt, übernimmt der Compiler: return; wird bei .version 6 zu STACK_ALLOC ; RETURN, return <expr>; wird ohne Präfix zu <expr> ; RETURN (wie bei Cavedogs Compiler).

Auf deiner eigenen Installation nachvollziehen:

kbot ctx use kingdoms  # or wherever you registered TAK
kbot cob roundtrip

Port-Symbole jenseits von Port 20

Skripte von TA: Kingdoms verwenden get_unit_value-Ports bis 46 (CURRENT_SPEED, VETERAN_LEVEL, Felder des Magie-Pools usw.). kbots Decompiler benennt derzeit nur die Ports 1–20 symbolisch (siehe Anhang B); TAK-Ports ≥ 21 werden als numerische Literale ausgegeben. Der Bytecode speichert sie ohnehin als Ganzzahlen, Round-Trips sind also nicht betroffen – nur die Lesbarkeit des BOS leidet. Die Ergänzung der TAK-Portnamen wird separat verfolgt.


Anhang A – Vollständige Opcode-Referenz

Jede Instruktion, die TA oder TA: Kingdoms ausführt. Maßgeblich ist formats/scripting/opcodes.go in kbot-io. Die Stack-Notation verwendet die Forth-Schreibweise ( before -- after ). Die Spalten Inline zeigen, wie viele uint32-Wörter auf das Opcode-Wort selbst folgen (beim Dekodieren verbraucht, nicht vom Stack).

Animation – Piece-Manipulation (0x100x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10001000 MOVE 2 (piece, axis) ( speed dist -- ) move <piece> to <axis> <dist> speed <speed>;
0x10002000 TURN 2 (piece, axis) ( speed dir -- ) turn <piece> to <axis> <dir> speed <speed>;
0x10003000 SPIN 2 (piece, axis) ( speed -- ) spin <piece> around <axis> speed <speed>;
0x10004000 STOP_SPIN 2 (piece, axis) ( decel -- ) stop-spin <piece> around <axis> [decel <d>];
0x10005000 SHOW 1 (piece) ( -- ) show <piece>;
0x10006000 HIDE 1 (piece) ( -- ) hide <piece>;
0x10007000 CACHE 1 (piece) ( -- ) cache <piece>;
0x10008000 DONT_CACHE 1 (piece) ( -- ) dont-cache <piece>;
0x10009000 PIECE_OP_09 1 (piece) ( a b -- ) __piece_op_09(<piece>, a, b); – TA führt es aus, aber seine Einheitenskripte machen nichts damit; kein Skript der Verkaufsversion verwendet es.
0x1000A000 DONT_SHADOW 1 (piece) ( -- ) dont-shadow <piece>; – TA führt es als No-op aus; siehe Anhang C.
0x1000B000 MOVE_NOW 2 (piece, axis) ( pos -- ) move <piece> to <axis> <pos> now;
0x1000C000 TURN_NOW 2 (piece, axis) ( angle -- ) turn <piece> to <axis> <angle> now;
0x1000D000 SHADE 1 (piece) ( -- ) shade <piece>;
0x1000E000 DONT_SHADE 1 (piece) ( -- ) dont-shade <piece>;
0x1000F000 EMIT_SFX 1 (piece) ( type -- ) emit-sfx <type> from <piece>;

Warteoperationen (0x101x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10011000 WAIT_FOR_TURN 2 (piece, axis) ( -- ) wait-for-turn <piece> around <axis>;
0x10012000 WAIT_FOR_MOVE 2 (piece, axis) ( -- ) wait-for-move <piece> along <axis>;
0x10013000 SLEEP 0 ( ticks -- ) sleep <ticks>;

Stack-Manipulation (0x102x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10021000 PUSH_IMM 1 (value) – PUSH ohne Flags: Das Spiel bricht daran ab (ta-push-flags). Wird vom Compiler nie geschrieben.
0x10021001 PUSH_CONST 1 (value) ( -- v ) <literal> in einem beliebigen Ausdruck
0x10021002 PUSH_LOCAL 1 (var#) ( -- v ) local_N in einem beliebigen Ausdruck
0x10021004 PUSH_STATIC 1 (var#) ( -- v ) global_N in einem beliebigen Ausdruck
0x10022000 STACK_ALLOC 0 ( -- ) (implizit – eines pro var und Parameter)
0x10023002 POP_LOCAL 1 (var#) ( v -- ) local_N = expr;
0x10023004 POP_STATIC 1 (var#) ( v -- ) global_N = expr;
0x10024000 POP_STACK 0 ( v -- ) (verwirft das Ergebnis eines Ausdrucks)

Arithmetik (0x103x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10031000 ADD 0 ( a b -- a+b ) a + b
0x10032000 SUB 0 ( a b -- a-b ) a - b
0x10033000 MUL 0 ( a b -- a*b ) a * b
0x10034000 DIV 0 ( a b -- a/b ) a / b
0x10035000 BITWISE_AND 0 ( a b -- a&b ) a & b
0x10036000 BITWISE_OR 0 ( a b -- a|b ) a | b
0x10037000 XOR 0 ( a b -- a^b ) a ^ b
0x10038000 NOT 0 ( a -- ~a ) ~a

TA hat keine Modulo-Instruktion: % ist in einem TA-Skript ein Kompilierfehler. 0x10039000–0x1003B000 sind die TAK_MATH_*-Operatoren von TA: Kingdoms (Anhang C); TA bricht an ihnen ab.

Abfragen an die Engine (0x104x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10041000 RAND 0 ( low high -- rnd ) rand(low, high)
0x10042000 GET_UNIT_VALUE 0 ( port -- value ) get <PORT>
0x10043000 GET 0 ( port a b c d -- value ) get <PORT>(a, b, …) / get(port, …) – holt immer einen Port und vier Argumente vom Stack; für fehlende legt der Compiler Nullen auf den Stack
0x10044000 IS_CARRYING_UNIT 0 ( unit -- bool ) __is_carrying_unit(unit) – 1, wenn die angegebene Einheit auf dieser mitfährt
0x10045000 CARRIER_UNIT_ID 0 ( -- id ) __carrier_unit_id() – die Einheit, die diese trägt, oder 0

Den Katalog der Ports findest du in Anhang B.

Vergleich und Logik (0x105x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10051000 LESS_THAN 0 ( a b -- a<b ) a < b
0x10052000 LESS_OR_EQUAL 0 ( a b -- a<=b ) a <= b
0x10053000 GREATER_THAN 0 ( a b -- a>b ) a > b
0x10054000 GREATER_EQUAL 0 ( a b -- a>=b ) a >= b
0x10055000 EQUAL 0 ( a b -- a==b ) a == b
0x10056000 NOT_EQUAL 0 ( a b -- a!=b ) a != b
0x10057000 LOGICAL_AND 0 ( a b -- a&&b ) a AND b
0x10058000 LOGICAL_OR 0 ( a b -- a||b ) a OR b
0x10059000 XOR_ALT 0 ( a b -- a^b ) a XOR b – ein bitweises XOR wie 0x10037000 (6 XOR 3 ergibt 5), kein logisches
0x1005A000 LOGICAL_NOT 0 ( a -- !a ) NOT a

Kontrollfluss und Nebenläufigkeit (0x106x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10061000 START_SCRIPT 2 (script#, argc) ( args… -- ) start-script Foo(a, b);
0x10062000 CALL_SCRIPT 2 (script#, argc) ( args… -- ) call-script Foo(a, b);
0x10063000 DISCARD_CALL 2 (word, argc) ( args… -- ) __discard_call(word, args…); – holt die Argumente vom Stack (mehr als vier laufen über den Puffer des Spiels hinaus) und führt nichts aus
0x10064000 JUMP 1 (offset) ( -- ) (Compiler – goto, Schleifen)
0x10065000 RETURN 0 ( v -- ) return v;
0x10066000 JUMP_IF_FALSE 1 (offset) ( v -- ) (Compiler – if/while)
0x10067000 SIGNAL 0 ( mask -- ) signal <mask>;
0x10068000 SET_SIGNAL_MASK 0 ( mask -- ) set-signal-mask <mask>;

offset ist bei JUMP / JUMP_IF_FALSE ein Byte-Offset relativ zu OffsetToScriptCode, nicht relativ zur aktuellen Instruktion.

Effekte (0x107x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10071000 EXPLODE 1 (piece) ( type -- ) explode <piece> type <type>;
0x10072000 PLAY_SOUND 1 (vol) ( sound -- result ) play-sound(<sound>, <vol>); – nur TA: Kingdoms (.version 6); TA bricht daran ab

Setz-Operationen (0x108x)

Opcode Mnemonic Inline Stack-Effekt BOS-Syntax
0x10082000 SET_VALUE 0 ( port value -- ) set <PORT_NAME> to <value>;
0x10083000 ATTACH_UNIT 1 (piece) ( unit -- ) attach-unit <unit> to <piece>;
0x10084000 DROP_UNIT 0 ( unit -- ) drop-unit <unit>;

Kodierung der Opcodes

Jedes Opcode-Wort hat die Form 0x10CCSSFF, wobei:

  • CC = das Kategorie-Byte (Animation, Mathematik, Kontrollfluss, …).
  • SS = das Unterbefehls-Nibble im oberen Nibble des zweithöchsten Bytes.
  • FF = das Flags-Byte.

Das Spiel verzweigt anhand von word & 0x100FF000 und ignoriert die übrigen Bits, ein Wort wie 0x10064001 läuft also als JUMP; der Disassembler schreibt es als JUMP@0x10064001, und der Assembler liest das wieder zum selben Wort ein. PUSH und POP lesen ihre Quelle bzw. ihr Ziel aus den unteren drei Bits: PUSH akzeptiert 1 (Konstante), 2 (lokal) und 4 (statisch), POP akzeptiert 2 und 4, und bei jedem anderen Wert bricht das Skript ab.

Wörter, die kein Spiel ausführt, stellt der Disassembler als UNKNOWN_0x10xxxxxx dar (und ta-unknown-opcode meldet sie); wenn du eines in einer echten Datei findest, eröffne bitte ein Issue mit dem COB als Beispiel.


Anhang B – Port-Tabelle von get_unit_value

Die 20 Ports, die kbots Decompiler derzeit symbolisch benennt. Das echte TA hat ~80; TA: Kingdoms fügt weitere ~15 für magiebezogene Größen hinzu. Alles oberhalb von Port 20 erscheint in der Ausgabe des Decompilers heute als numerisches Literal.

Port Symbolischer Name Liefert Anmerkungen
1 ACTIVATION bool Ist der „Aktiv“-Schalter der Einheit an (Radar, Störsender, Befestigungen)?
2 STANDINGMOVEORDERS enum 0 = Position halten, 1 = manövrieren, 2 = umherstreifen.
3 STANDINGFIREORDERS enum 0 = Feuer halten, 1 = Feuer erwidern, 2 = Feuer frei.
4 HEALTH 0–100 Verbleibender Prozentsatz von MaxDamage.
5 INBUILDSTANCE bool Fährt die Einheit gerade ihre Nano-Lathes aus?
6 BUSY bool Ist die Einheit gerade mit einem Befehl beschäftigt?
7 PIECE_XZ gepackt Kombiniertes X/Z eines Piece (kodiert). Wird zum Zielen von Projektilen verwendet.
8 PIECE_Y int Y-Koordinate eines Piece relativ zum Ursprung der Einheit.
9 UNIT_XZ gepackt Kombiniertes X/Z der Einheit.
10 UNIT_Y int Y-Koordinate der Einheit.
11 UNIT_HEIGHT int Höhe über dem Gelände.
12 XZ_ATAN int atan2 zwischen zwei XZ-kodierten Werten (zum Ausrichten von Geschütztürmen).
13 XZ_HYPOT int Hypotenuse zwischen zwei XZ-kodierten Werten.
14 ATAN int atan2(y, x) zweier auf den Stack gelegter Werte.
15 HYPOT int hypot(x, y).
16 GROUND_HEIGHT int Geländehöhe an der Position der Einheit.
17 BUILD_PERCENT_LEFT 0–100 Wird von SmokeUnit verwendet, um den Baufortschritt zu kennen. 0, sobald fertig gebaut.
18 YARD_OPEN bool Für Gebäude mit Yardmap: Sind die Tore offen?
19 BUGGER_OFF bool Wird von der Engine gesetzt, wenn die Einheit aus dem Weg fahren muss.
20 ARMORED bool Ist die gepanzerte Haltung der Einheit aktiv (Bulldog, Goliath usw.)?

Zu den höheren Ports gehören CURRENT_SPEED (29 – berüchtigt dafür, dass der Cavedog-Scriptor ihn als Literal einsetzt; siehe Stolperfallen oben), MAX_ID, UNIT_TEAM, UNIT_BUILD_PERCENT, BUILDER, UNIT_ALLIED und viele mehr. Die vollständige Liste steht in Cavedogs interner Dokumentation; das Community-Wiki in den TAUniverse-Foren hat den umfassendsten öffentlichen Katalog.

TA: Kingdoms erweitert den Portbereich. Die reinen TAK-Ports, die kbot erkennt (der Decompiler gibt den symbolischen Namen aus, der Compiler akzeptiert ihn als Eingabe), stammen aus der Tabelle [UNITVLAUES] von Scriptor:

Port Symbol Anmerkungen
21 WEAPON_AIM_ABORTED Wird gesetzt, wenn die Zielberechnung einer Waffe abgebrochen wird.
22 WEAPON_READY Die Waffe ist nachgeladen und feuerbereit.
23 WEAPON_LAUNCH_NOW Die Engine hat die Waffe abgefeuert.
26 FINISHED_DYING Die Todesanimation ist abgeschlossen.
27 ORIENTATION Blickrichtung der Einheit.
28 IN_WATER Die Einheit ist im Wasser.
29 CURRENT_SPEED Aktuelle Bewegungsgeschwindigkeit (mit TA gemeinsam – Scriptor nummeriert ihn hier genauso).
31 MAGIC_DEATH Die Einheit starb durch Magieschaden.
32 VETERAN_LEVEL Veteranenstufe.
34 ON_ROAD Die Einheit steht auf einer Straßenkachel.

Anhang C – Opcodes von TA: Kingdoms

Note

Opcodes nur in TA:K. Keiner davon kommt in .cob-Dateien der TA-Verkaufsversion vor. DONT_SHADOW und MISSION_COMMAND verwenden die kanonischen Mnemonics von Scriptor (Switecks TAK-fähigem Compiler/Decompiler); die Mathe-Operationen haben keine dokumentierte Semantik – Scriptor bezeichnet sie als ?? und ????, daher behält kbot die Platzhalter TAK_MATH_09 / TAK_MATH_0A / TAK_MATH_0B bei. Die Skripte der Verkaufsversion von TA: Kingdoms verwenden jeden davon als binären Operator (zwei Werte vom Stack, einer zurück), wenn sie Bitfelder packen und entpacken. TA 3.1c hat keinen Handler für PLAY_SOUND, MISSION_COMMAND oder die Mathe-Operatoren; DONT_SHADOW führt es als No-op aus.

Opcode Mnemonic Inline Vorkommen in der TAK-Verkaufsversion Anmerkungen
0x1000A000 DONT_SHADOW 1 (piece) 66 Schaltet den Schattenwurf für ein einzelnes Piece ab. Steht in der Animationskategorie neben DONT_SHADE (0x1000E000) – kbot nannte es früher TAK_ANIM_0A. Gleiche Form auf der Platte wie die anderen ANIM_*-Operationen: 4-Byte-Opcode + ein piece-DWORD. BOS-Schlüsselwort: dont-shadow <piece>;.
0x10039000 TAK_MATH_09 0 264 Binärer Operator der Mathe-Kategorie, insgesamt 4 Byte: ( a b -- r ). BOS: __tak_math_09(a, b).
0x1003A000 TAK_MATH_0A 0 – Gleiche Form. Manche Opcode-Tabellen nennen ihn BITWISE_NOT; das bitweise NOT von TA ist 0x10038000. BOS: __tak_math_0a(a, b).
0x1003B000 TAK_MATH_0B 0 66 Gleiche Form wie TAK_MATH_09. BOS: __tak_math_0b(a, b).
0x10073000 MISSION_COMMAND 2 (soundNameIdx, argCount) 3.885 Aufruf eines Engine-Befehls. Das erste Inline-DWORD ist ein Index in die Tabelle SoundNames des COB; das zweite ist die Anzahl der Werte, die als Argumente vom Stack geholt werden. Der Opcode legt ein einzelnes Ergebnis zurück auf den Stack (das meist von einem folgenden POP_STACK verworfen oder per POP_STATIC/POP_LOCAL gespeichert wird). Mit Abstand der häufigste Opcode, den es nur in TAK gibt. BOS-Schlüsselwort: Mission-Command("name", args…) – entspricht der kanonischen Syntax von Scriptor.

Kodierung (Zusammenfassung)

Jedes COB-Opcode-Wort hat die Form 0x10CCSSFF, wobei CC die Kategorie und SS der Unterbefehl ist – diese TAK-Opcodes belegen einfach bisher ungenutzte CC:SS-Plätze innerhalb der bestehenden Kategorien, statt ein neues Kategorie-Byte einzuführen. DONT_SHADOW liegt in der Animationskategorie neben DONT_CACHE/DONT_SHADE, MISSION_COMMAND in der Effektkategorie neben EXPLODE und PLAY_SOUND.

Tip

Mit echtem Bytecode abgleichen.

kbot ctx use tak-30bb-flat
kbot cob disassemble scripts/araat.cob -a | grep -E 'DONT_SHADOW|MISSION|TAK_MATH|PLAY_SOUND'

Echte Beispiele im Referenzkatalog

Echte Einheiten, die die Extreme des Formats ausreizen – öffne sie, um die tatsächliche Komplexität von Skripten zu sehen statt Beispielen, die für den Text zurechtgelegt wurden:

  • CORKROG – Krogoth (Core) – eines der größten COBs im Spiel (~25 KB, über 30 Skripte, komplexe Animationen über viele Pieces).
  • ARMCOM – Arm Commander – repräsentative „reichhaltige“ Einheit: Zyklen für Laufen/Zielen/Ruhe/Bauen, mehrere Waffen, Behandlung der D-Gun.
  • CORTRUCK – Construction Truck – nahezu minimales COB, das auf dieser Seite als durchgehendes Beispiel dient (3 Skripte: SmokeUnit, Create, Killed).
  • ARMSAM – Samson – gutes Beispiel für eine Einheit mit Geschützturm (turret1 dreht sich zum Zielen, Rückstoßanimation, Synchronisation per wait-for-turn).

Dekompiliere jedes davon lokal mit kbot cob decompile scripts/<name>.cob, sobald ein kbot-Kontext gesetzt ist.


Siehe auch

  • 3DO – die Piece-Namen, auf die COB-Skripte verweisen, stehen in der .3do der Einheit.
  • FBI – die Metadatendatei der Einheit. ObjectName verbindet dort das COB mit einem 3DO; Sounds legt die Soundbank fest, die das Skript abspielen kann.
  • Die Regeltabelle von cob lint in der kbot-CLI.
  • Glossar – piece, axis, signal mask, tick.