모든 형식 페이지 ↓
유닛·스크립트·설정

COB / BOS — 유닛 스크립트 바이트코드

출처: coreprime/kbot @ 0ab97bf · GitHub에서 편집

Total Annihilation의 모든 유닛은 스크립트를 실행합니다. 스크립트는 유닛의 피스(piece)를 움직이고, 무기 발사 섬광을 내고, 효과음을 재생하고, 게임 이벤트에 반응합니다. 소스 언어는 BOS("Basic Object Script")이고, 엔진이 실제로 실행하는 컴파일된 바이트코드는 COB("Cobble" — "compiled BOS"를 비튼 Cavedog의 말장난)입니다. 유닛마다 파일이 하나씩 있으며, 이름은 해당 유닛 FBI의 ObjectName을 따릅니다.

Tip

직접 해 보세요.

# 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/

모든 플래그와 린트 규칙 전체 표는 CLI kbot cob 레퍼런스를 참고하세요.

Go에서 쓰기. kbot-io의 formats/scripting을 사용합니다.

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)
}

두 언어, 하나의 파일 형식

단계 형식 도구
소스 BOS — C와 비슷한 구조적 언어 손으로 작성; Switeck의 Scriptor; kbot cob decompile로 COB에서 왕복 변환
컴파일 결과 COB — 스택 기반 바이트코드 엔진이 로드; Scriptor나 kbot cob compile로 생성

엔진이 보는 것은 COB뿐입니다. BOS는 사람이 작성하기 편하도록 있는 것으로, "유닛의 스크립트를 편집한다"는 것은 실제로는 BOS 소스를 편집하고, COB로 다시 컴파일하고, 그 COB를 유닛의 애드온 .ufo에 넣어 배포한다는 뜻입니다.

최소한의 예:

// 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은 이것을 바이너리 덩어리로 낮춥니다. kbot cob disassemble -a는 옵코드 스트림을 제어 흐름 화살표와 나란히 보여 주고, kbot cob decompile은 반대 방향으로 BOS를 복원합니다. 복원된 BOS는 거의 언제나 바이트 단위로 동일하게 왕복 변환(round-trip)됩니다(kbot cob roundtrip이 게임 전체 카탈로그에 걸쳐 이를 검증합니다).

소스는 .version 6으로 시작하지 않는 한 TA 스크립트(COB 버전 4)로 컴파일됩니다. 컴파일러는 TA 3.1c가 잘못 실행할 코드를 만나면 자리 표시자를 써 넣는 대신 거부합니다. TA: Kingdoms 구문(play-sound, Mission-Command, __tak_math_*, .sound_name)과 %는 TA 스크립트에서 오류입니다(TA에는 나머지 연산 명령이 없으며, 예전에 %가 변환되던 워드 0x10037000은 TA에서 비트 XOR입니다). 알 수 없는 식별자, 그리고 게임의 스택 슬롯 32개를 넘게 필요로 하는 함수도 마찬가지로 오류입니다. 컴파일은 되지만 살펴볼 필요가 있는 것 — 두 번 정의된 함수(게임의 이름 조회처럼 모든 호출이 첫 번째 정의에 연결됨) — 은 TA 호환성 린트 규칙에서 발견된 내용과 함께 경고로 출력됩니다. --strict를 주면 경고가 있을 때 명령이 실패합니다. kbot cob assemble은 손으로 작성한 리스팅에도 같은 검사를 적용합니다.


COB의 디스크상 구조

Important

두 가지 방언. 이 절은 Total Annihilation이 사용하는 방언인 COB v4를 설명합니다. TA: Kingdoms는 COB v6(VersionSignature == 6) 방언을 쓰는데, 아래 내용을 모두 유지하면서 헤더를 8바이트 늘리고 피스 이름 배열 뒤에 명령 문자열 오프셋 테이블을 하나 더 넣습니다. v6의 세부 사항은 TA: Kingdoms — COB v6 차이점을 참고하세요. 옵코드 목록은 두 방언 모두에 적용되며, TAK 전용 옵코드는 부록 C에 따로 정리되어 있습니다.

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

표준 필드 이름은 Scriptor의 HEADER_COB 구조체와 같습니다. 예전 kbot 버전은 LengthOfScripts / NumberOfStaticVars / UKZero / OffsetToNameArray를 "Unknown0/1/2/3"이라고 불렀습니다. 오프셋은 같고, 이제 문서로 확인된 정체를 반영했을 뿐입니다.

그 뒤에는 다음 구조가 이어집니다.

┌─ 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

스크립트 찾기

코드 인덱스 테이블에는 바이트 오프셋이 아니라 uint32 워드 오프셋이 들어 있습니다. i번 스크립트의 바이트코드를 찾으려면:

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

스크립트는 다음 스크립트가 시작하는 곳에서 끝납니다(마지막 스크립트는 인덱스 테이블이 시작하는 곳에서 끝납니다).

Important

**이름 테이블의 문자열 오프셋은 문자열 풀 기준이 아니라 파일 기준 절대 오프셋입니다.** Cavedog의 도구는 문자열 풀을 옮긴 적이 없어서 실제로는 문제가 되지 않았지만, COB를 손으로 재배치한다면 모든 오프셋을 고쳐야 합니다.


옵코드 스트림

COB는 스택 기반 가상 머신입니다. 대부분의 옵코드는 스크립트별 데이터 스택에서 인수를 꺼내(pop) 처리한 뒤 결과를 다시 넣습니다(push). 일부 옵코드는 인라인 피연산자(점프 대상, 피스 번호)를 가집니다.

각 옵코드는 다음 구조의 uint32 하나입니다.

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

최상위 바이트는 항상 0x10이고, family/op/flags는 옵코드마다 다릅니다. 게임은 워드 전체를 비교하지 않고 word & 0x100FF000으로 분기합니다. 따라서 이 마스크 밖의 비트는 무시되며 0x10064001은 JUMP로 실행됩니다(kbot의 디스어셈블러는 이런 워드를 JUMP@0x10064001로 써서 다시 어셈블해도 그대로 나오게 합니다). PUSH(0x10021000)와 POP(0x10023000)은 하위 3비트에서 출처나 대상을 읽습니다. kbot-io의 scripting/opcodes.go가 목록이며, scripting.Opcodes()는 모든 명령을 인라인 피연산자, 스택 효과와 함께 나열합니다.

옵코드 계열

계열 예 비고
0x100x MOVE, TURN, SPIN, SHOW, HIDE, EMIT_SFX 피스 애니메이션. 피스 번호와 축 번호가 인라인으로 뒤따릅니다.
0x101x WAIT_FOR_TURN, WAIT_FOR_MOVE, SLEEP 비동기 대기 지점 — 조건이 충족될 때까지 스크립트가 실행을 양보합니다.
0x102x PUSH_CONST, PUSH_LOCAL, PUSH_STATIC, POP_* 스택 조작.
0x103x ADD, SUB, MUL, DIV, BITWISE_AND, BITWISE_OR, XOR, NOT 산술·비트 연산. 나머지 연산은 없습니다.
0x104x RAND, GET_UNIT_VALUE, GET, IS_CARRYING_UNIT, CARRIER_UNIT_ID 엔진 조회.
0x105x <, <=, >, >=, ==, !=, &&, ||, XOR_ALT, ! 비교·논리 연산. XOR_ALT는 논리 XOR가 아니라 비트 XOR입니다.
0x106x START_SCRIPT, CALL_SCRIPT, DISCARD_CALL, JUMP, RETURN, JUMP_IF_FALSE, SIGNAL, SET_SIGNAL_MASK 제어 흐름과 동시성.
0x107x EXPLODE, PLAY_SOUND 특수 효과. PLAY_SOUND는 TA: Kingdoms 전용입니다.
0x108x SET_VALUE, ATTACH_UNIT, DROP_UNIT 엔진 상태 변경.

작지만 대표적인 예시:

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

점프 오프셋은 (현재 명령이 아니라) 스크립트 코드 섹션의 시작을 기준으로 한 바이트 오프셋입니다. JUMP 0x4C는 "OffsetToScriptCode + 0x4C로 점프"라는 뜻입니다. 디스어셈블러의 -a 플래그는 대응하는 명령 사이에 화살표를 그려 이를 한눈에 보여 줍니다.


동시성 모델

한 유닛에서 여러 스크립트가 동시에 실행될 수 있습니다 — Walk, Aim, SmokeUnit, MoveWatcher 등. 이들은 협력형으로 스케줄링됩니다.

  • **sleep <ticks>**는 N틱(틱당 1/30초) 동안 실행을 양보합니다.
  • **wait-for-turn <piece> around <axis>**는 해당 피스가 현재 turn 애니메이션을 마칠 때까지 양보합니다. wait-for-move도 마찬가지입니다.
  • **signal <n>**은 signal_mask & n != 0인 스크립트를 모두 종료합니다.
  • **set-signal-mask <n>**은 현재 스크립트가 어떤 시그널에 반응할지 선언합니다.

시그널 마스크는 스크립트가 선점(preemption)을 구현하는 수단입니다. MoveWatcher는 마스크 SIG_MOVE를 설정하고 속도를 감시하다가, 유닛이 멈추면 SIG_MOVE를 보내 걷기 애니메이션을 깔끔하게 종료한 뒤 대기 자세를 시작합니다.

Warning

SET_SIGNAL_MASK는 관심을 등록할 뿐 — 아무것도 종료하지 않습니다. 마스크 X가 설정된 스크립트를 멈추는 것은 signal X를 보내는 동작입니다. kbot cob lint의 signal-never-signalled 린트 규칙은 아무도 보내지 않는 시그널을 마스크로 설정한 스크립트를 잡아냅니다(이는 거의 언제나 버그입니다).

선점이 실제로 일어나는 과정

흔한 패턴: MoveUnit이 Walk 애니메이션 스크립트를 시작하고, 이동이 멈추면 엔진이 SIG_MOVE를 보내 Idle이 시작되기 전에 깔끔하게 정리합니다. 순서는 다음과 같습니다.

   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)

눈여겨볼 점 세 가지:

  1. 시그널이 발생하면 SIG_MOVE를 설정한 두 스크립트가 모두 종료됩니다. MoveUnit 자신도 포함됩니다(상관없습니다. 할 일은 이미 끝났으니까요).
  2. 시그널을 보내는 스크립트(MoveUnit)는 종료되기 전에 시그널을 보낼 만큼은 살아 있습니다. 런타임은 signal 옵코드가 반환된 뒤에 마스크를 검사합니다.
  3. Idle은 SIG_MOVE를 설정하지 않으므로 다음 signal SIG_MOVE의 영향을 받지 않습니다.

실제 예제 — 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);
}

대응하는 디스어셈블 결과(kbot cob disassemble -a -s SmokeUnit)는 실제 옵코드 스트림을 보여 주며, while/if 본문 사이로 JUMP_IF_FALSE/JUMP 화살표가 이어집니다. 디컴파일러 출력이 모호할 때 유용합니다.


린트

kbot cob lint(그리고 에셋 탐색기의 Lint 탭)는 kbot-io의 기본 규칙 집합을 실행합니다. Cavedog 자체 스크립트와 커뮤니티가 제출한 모드 패치에서 실제로 나온 버그를 바탕으로 한 스타일 규칙, 파일 수준 검사 두 가지, 그리고 버전 6을 선언하지 않은 모든 COB에 대해서는 TA 3.1c 호환성 규칙이 포함됩니다. 스타일 규칙 목록:

규칙 심각도 잡아내는 것
unused-piece warning 선언되었지만 어느 스크립트에서도 쓰이지 않는 피스.
unused-static / unused-local warning 할당되었지만 한 번도 읽히지 않는 변수.
always-true / dead-code info / warning if(1) / while(0).
long-function / high-complexity warning 명령 100개 초과 또는 순환 복잡도 15 초과.
invalid-call error 존재하지 않는 함수에 대한 call-script.
speed-zero warning speed <0>인 move/turn — 애니메이션이 끝나지 않습니다.
empty-function info 함수 본문이 return 0뿐입니다.
duplicate-animation warning 똑같은 애니메이션 명령 두 개가 연달아 있습니다.
sleep-only-guard info if 블록 안에 sleep만 있습니다.
duplicate-if info 같은 조건의 if가 연달아 두 번 있습니다.
raw-signal info signal SIG_AIM 대신 signal 4.
unnamed-global info 이름 붙은 정적 변수 대신 global_3.
signal-never-signalled warning 아무도 보내지 않는 시그널을 스크립트가 마스크로 설정합니다.
recursive-call warning call-script 순환.
duplicate-function warning 두 스크립트의 이름이 같습니다. 게임은 첫 번째만 호출합니다.
malformed-cob error / warning 파일이 로드되지 않거나 스크립트 코드가 잘려 있습니다(error). 리더가 허용한 이름 테이블 문제(warning).

TA 호환성 규칙은 TA 3.1c가 오류로 멈추거나 다르게 실행하는 것을 보고합니다. 이 규칙들은 오류(error)이며, 다른 규칙처럼 --ci SARIF 출력에 나타나고, TA: Kingdoms(버전 6) 파일은 건너뜁니다.

규칙 잡아내는 것
ta-kingdoms-opcode TA 스크립트 안의 PLAY_SOUND, MISSION_COMMAND 또는 TAK_MATH_* 연산자. TA에는 이들을 처리할 핸들러가 없어 스크립트가 해당 명령에 도달하면 오류로 멈춥니다.
ta-unknown-opcode 어느 게임도 실행하지 않는 옵코드 워드.
ta-push-flags 하위 비트가 1(상수), 2(지역 변수), 4(정적 변수)가 아닌 PUSH, 또는 하위 비트가 2나 4가 아닌 POP — 일부 도구가 써 넣은 플래그 없는 0x10021000과 0x10021008을 포함합니다.
ta-stack-limit STACK_ALLOC이 32개를 넘거나, 지역 변수와 대기 중인 값의 합이 게임의 스택 슬롯 32개를 넘는 경우. 게임은 이를 검사하지 않고 스크립트 뒤쪽 메모리를 덮어씁니다.
ta-get-arguments 대기 중인 값이 다섯 개보다 적은 상태에서 도달하는 GET. GET은 항상 포트 하나와 인수 네 개를 꺼내므로, 인수가 하나인 get은 나머지를 함수의 지역 변수에서 가져가 덮어씁니다.
ta-stack-underflow 대기 중인 값보다 많은 값을 꺼내는 그 밖의 모든 명령, 또는 스택 깊이가 서로 다른 채로 합쳐지는 경로.
ta-discard-call 인수가 네 개를 넘는 DISCARD_CALL.

규칙에 대한 자세한 설명은 kbot README에 있습니다. 디컴파일러는 원래의 변수와 피스 번호를 일부러 보존하므로, 디컴파일→린트→수정→컴파일 사이클을 돌리면 손볼 필요가 있는 모드가 드러납니다.


주의할 점

Warning

call-script는 0x10062000입니다. 0x10063000은 다른 명령인 DISCARD_CALL입니다. 같은 인라인 워드 두 개(스크립트 번호와 인수 개수)를 가지지만, 인수를 꺼내기만 하고 아무것도 실행하지 않습니다. 정식 출시판 스크립트에서는 쓰이지 않으며, kbot의 디컴파일러는 이를 __discard_call(word, args…)로 씁니다.

Warning

공개된 여러 옵코드 표가 비트 연산 워드의 이름을 잘못 적고 있습니다. TA는 0x10037000을 비트 XOR로 실행하고(나머지 연산이 아닙니다 — TA에는 나머지 연산 명령이 없습니다), 0x10038000은 단항 비트 NOT으로(XOR가 아닙니다), 0x10059000은 두 번째 비트 XOR로(논리 XOR가 아닙니다: 6 XOR_ALT 3은 5) 실행합니다. 0x1003A000은 TA: Kingdoms 연산자로, TA에서는 오류로 멈춥니다. 예전 이름(MOD, BITWISE_XOR, BITWISE_NOT, LOGICAL_XOR)으로 작성한 리스팅도 원래의 워드로 그대로 어셈블됩니다.

  • COB 헤더의 오프셋은 모두 파일 기준 절대 오프셋이지만, ScriptCodeIndexArray의 항목은 코드 섹션 안의 워드 오프셋입니다. 헷갈리기 쉽습니다.
  • 옵코드 안의 피스 번호는 문자열이 아니라 피스 이름 테이블의 인덱스입니다. COB는 이름을 문자열 풀로 담고 있고, 런타임은 속도를 위해 인덱스를 씁니다.
  • get UNIT_VALUE 옵코드는 TA와 TA: Kingdoms 사이에 미묘하게 다릅니다. TA: Kingdoms는 마법 재생, 명령 포인트 등을 위한 값 ID를 추가했고, TA의 값 테이블은 더 작습니다. kbot의 디컴파일러는 현재 TA 집합에 이름을 붙이며, TAK의 21 이상 포트 번호는 숫자 리터럴로 출력합니다(이 경우에도 어셈블러를 통한 왕복 변환은 그대로 됩니다). 차이 전체는 TA: Kingdoms — COB v6 차이점을 참고하세요.
  • 변수는 두 종류입니다: local_N(함수 지역 스택 슬롯)과 global_N(호출 간에 유지되는 유닛별 정적 변수). 디컴파일러는 기본적으로 local_N / global_N을 출력하니, 읽기 쉽도록 소스에서 이름을 바꾸세요.
  • Cavedog Scriptor 도구는 가끔 CURRENT_SPEED를 리터럴 29로 인라인해 버려서 왕복 변환 후 MoveWatcher 스타일 스크립트를 망가뜨립니다. kbot의 디컴파일러는 기호 이름을 복원합니다. Scriptor를 거쳐 왕복 변환해야 한다면 괄호로 감싸세요: (get CURRENT_SPEED) > 5.

일반적인 크기

항목 Cavedog scripts/*.cob에서 관찰된 범위
파일 크기 0.5 KB – 25 KB
헤더 오버헤드 항상 44바이트
유닛당 스크립트 수 3 – 30(일반적인 유닛: 5–10)
유닛당 피스 수 3 – 80(Krogoth ≈ 80)
코드 섹션 크기 300 B – 20 KB(복잡도에 따라 크게 다름)
armcom.cob(커맨더) 약 14 KB, 스크립트 30개
cortruck.cob(단순한 유닛) 약 750 B, 스크립트 3개

TA: Kingdoms — COB v6 차이점

Note

이 절은 TA:K 전용 차이점입니다. 위의 내용은 모두 COB v4(TA)를 설명합니다. TA: Kingdoms는 같은 스택 VM, 같은 문자열 풀, 같은 옵코드 인코딩 방식, 같은 스크립트 이름 / 피스 이름 체계를 유지합니다. 다른 점은 확장된 헤더, 피스 이름 배열 뒤에 덧붙은 추가 문자열 테이블, 그리고 새 옵코드 네 개입니다.

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)

8바이트 서브헤더는 OffsetToNameArray(44바이트 지점에서 끝나는 표준 11-DWORD 헤더의 끝)와 코드 섹션의 시작(OffsetToScriptCode, 보통 0x34 = 52) 사이에 있습니다. 리틀 엔디언 uint32 두 개를 담습니다.

필드 의미
[0] 사운드 이름 오프셋 테이블의 파일 기준 절대 오프셋. 항상 OffsetToPieceNameOffsetArray + NumPieces × 4와 같습니다 — 즉 피스 이름 오프셋 배열 바로 다음 바이트입니다. 표준 구조에서 계산할 수 있는 중복 정보이므로 라이터가 다시 만들어 냅니다.
[1] 테이블에 있는 사운드 이름의 개수. 이 값이 기준이 되는 개수입니다.

사운드 이름 오프셋 테이블의 각 항목은 문자열 풀 안에 있는 널 종료 문자열의 파일 기준 절대 오프셋입니다 — "SetMission o 1, s", "create ARASWORD", "ARROW10" 같은 것들입니다. 바이트코드는 이들을 오프셋이 아니라 인덱스로, MISSION_COMMAND 옵코드를 통해 참조합니다(부록 C 참고). 이 테이블을 사운드 이름 배열이라고 부르는 이유는 표준 Scriptor 컴파일러가 ADDSOUND 동작으로 문자열을 여기에 등록하기 때문입니다. 실제 내용은 오디오 파일 이름이 아니라 엔진 명령 문자열입니다.

kbot의 표현 방식

scripting.COB는 사운드 이름을 구조화된 []string 필드로 담습니다 — 불투명한 바이트를 그대로 보존하지 않습니다. 나머지(서브헤더 값, 오프셋 테이블, 풀 안의 문자열별 오프셋)는 쓸 때 이 목록에서 다시 만들어집니다.

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

v4 파일에서는 이 슬라이스가 항상 nil이며, 서브헤더와 사운드 이름 오프셋 테이블 모두 출력에서 생략됩니다.

디스어셈블러 / 디컴파일러는 각 문자열을 .sound_name 지시문으로 출력하므로, .coba와 .bos를 거치는 왕복 변환에서도 살아남습니다.

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

새 옵코드

TAK의 .cob 파일은 TA 3.1c에 핸들러가 없는 명령(PLAY_SOUND, MISSION_COMMAND, TAK_MATH_* 연산자)과, TA에서는 아무 동작도 하지 않는(no-op) DONT_SHADOW를 사용합니다. 게임 쪽의 정확한 의미는 문서화되어 있지 않지만, 바이너리 구조(크기 + 인라인 매개변수 개수)는 모든 정식 출시판 TAK .cob에서 확인되었고, 이것만으로도 디스어셈블→어셈블 왕복 변환에서 원래 바이트를 그대로 복원하기에 충분합니다. 전체 레퍼런스는 부록 C를 참고하세요.

왕복 변환 현황

파이프라인 TA TA: Kingdoms
disassemble → assemble(바이트 단위 동일) 278 / 278 ✅ 267 / 267 ✅
decompile → compile(바이트 단위 동일) 278 / 278 ✅ 단순한 유닛 스크립트와 상당수의 미션 COB(≈ 70 / 267)는 바이트 단위로 완벽하게 왕복 변환됩니다. 나머지 실패는 디컴파일러가 구조화된 if/while을 재구성하면서 고른 JUMP 배치를 컴파일러가 약간 다르게 다시 생성하는 제어 흐름 패턴입니다 — 바이트코드는 의미상 동등하지만(모든 TAK 옵코드, 명령 문자열, 서브헤더, v6 버전 워드가 보존됨) 바이트 단위로 같지는 않습니다. 단순하지 않은 스크립트라면 어떤 디컴파일→컴파일 사이클에서도 같은 종류의 불일치가 생깁니다.

TAK 확장을 위한 BOS 문법

디컴파일러는 TAK COB가 BOS를 거쳐 왕복 변환되도록 다음 의사 호출을 출력합니다. 컴파일러도 같은 문법을 받아들이지만, .version 6 뒤에서만 받아들입니다(dont-shadow는 예외 — TA는 이를 no-op으로 실행합니다). dont-shadow와 Mission-Command는 Scriptor의 표준 TAK 키워드를 씁니다. 수학 옵코드는 Scriptor에 문서화된 이름이 없으므로(Scriptor는 ??와 ????로 표시) kbot은 __tak_math_* 자리 표시자를 유지합니다.

// 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

Mission-Command의 첫 번째 인수는 따옴표로 감싼 사운드 이름 문자열입니다 — 컴파일러는 이를 사운드 이름 테이블에서 찾습니다(따라서 대응하는 .sound_name 지시문이 파일 앞쪽에 있어야 합니다). 나머지 인수는 스택에 넣어지고 엔진이 소비합니다.

v6의 나머지 메타데이터는 파일 맨 위의 두 지시문이 담습니다.

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

.version은 항상 출력됩니다(그래서 v4 TA 파일도 같은 파이프라인을 똑같이 왕복합니다). 각 .sound_name은 COB별 사운드 이름 테이블의 항목 하나입니다. 라이터는 len(SoundNames)와 항목이 나타나는 순서로 8바이트 서브헤더와 사운드 이름 오프셋 테이블을 다시 만듭니다.

v6에서 값이 없는 return;마다 생성되는 TAK의 STACK_ALLOC ; RETURN 에필로그는 컴파일러가 처리합니다. .version 6일 때 return;은 STACK_ALLOC ; RETURN으로, return <expr>;은 접두 없이 <expr> ; RETURN으로 낮춰집니다(Cavedog 컴파일러와 같음).

자신의 설치본에서 재현해 보세요.

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

20번을 넘는 포트 기호

TA: Kingdoms 스크립트는 46번까지의 get_unit_value 포트를 사용합니다(CURRENT_SPEED, VETERAN_LEVEL, 마법 풀 관련 필드 등). kbot의 디컴파일러는 현재 1–20번 포트만 기호로 표시하며(부록 B 참고), TAK의 21번 이상 포트는 숫자 리터럴로 출력합니다. 바이트코드는 어느 쪽이든 정수로 저장하므로 왕복 변환에는 영향이 없고 — BOS의 가독성만 떨어집니다. TAK 포트 이름 추가는 별도로 추적하고 있습니다.


부록 A — 전체 옵코드 레퍼런스

TA나 TA: Kingdoms가 실행하는 모든 명령입니다. 기준은 kbot-io의 formats/scripting/opcodes.go입니다. 스택 표기는 Forth 스타일의 ( before -- after )를 씁니다. 인라인 열은 옵코드 워드 자체 뒤에 오는 uint32 워드의 개수를 나타냅니다(스택이 아니라 디코딩 중에 소비됨).

애니메이션 — 피스 조작(0x100x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
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가 실행은 하지만 유닛 스크립트에서는 아무 효과가 없으며, 정식 출시판 스크립트에서는 쓰이지 않습니다.
0x1000A000 DONT_SHADOW 1(piece) ( -- ) dont-shadow <piece>; — TA는 no-op으로 실행합니다. 부록 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>;

대기 연산(0x101x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
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>;

스택 조작(0x102x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
0x10021000 PUSH_IMM 1(value) — 플래그 없는 PUSH: 게임이 오류로 멈춥니다(ta-push-flags). 컴파일러는 절대 생성하지 않습니다.
0x10021001 PUSH_CONST 1(value) ( -- v ) 모든 식 안의 <literal>
0x10021002 PUSH_LOCAL 1(var#) ( -- v ) 모든 식 안의 local_N
0x10021004 PUSH_STATIC 1(var#) ( -- v ) 모든 식 안의 global_N
0x10022000 STACK_ALLOC 0 ( -- ) (암묵적 — var와 매개변수마다 하나씩)
0x10023002 POP_LOCAL 1(var#) ( v -- ) local_N = expr;
0x10023004 POP_STATIC 1(var#) ( v -- ) global_N = expr;
0x10024000 POP_STACK 0 ( v -- ) (식의 결과를 버림)

산술(0x103x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
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에는 나머지 연산 명령이 없습니다. TA 스크립트에서 %는 컴파일 오류입니다. 0x10039000–0x1003B000은 TA: Kingdoms의 TAK_MATH_* 연산자이며(부록 C), TA에서는 오류로 멈춥니다.

엔진 조회(0x104x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
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, …) — 항상 포트 하나와 인수 네 개를 꺼냅니다. 빠진 인수는 컴파일러가 0을 넣습니다
0x10044000 IS_CARRYING_UNIT 0 ( unit -- bool ) __is_carrying_unit(unit) — 해당 유닛이 이 유닛에 타고 있으면 1
0x10045000 CARRIER_UNIT_ID 0 ( -- id ) __carrier_unit_id() — 이 유닛을 싣고 있는 유닛, 없으면 0

포트 목록은 부록 B를 참고하세요.

비교·논리(0x105x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
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 — 0x10037000과 같은 비트 XOR이며(6 XOR 3은 5), 논리 XOR가 아닙니다
0x1005A000 LOGICAL_NOT 0 ( a -- !a ) NOT a

제어 흐름·동시성(0x106x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
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…); — 인수를 꺼내기만 하고(네 개를 넘으면 게임의 버퍼를 넘침) 아무것도 실행하지 않습니다
0x10064000 JUMP 1(offset) ( -- ) (컴파일러 — goto, 반복문)
0x10065000 RETURN 0 ( v -- ) return v;
0x10066000 JUMP_IF_FALSE 1(offset) ( v -- ) (컴파일러 — if/while)
0x10067000 SIGNAL 0 ( mask -- ) signal <mask>;
0x10068000 SET_SIGNAL_MASK 0 ( mask -- ) set-signal-mask <mask>;

JUMP / JUMP_IF_FALSE의 offset은 현재 명령이 아니라 OffsetToScriptCode를 기준으로 한 바이트 오프셋입니다.

효과(0x107x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
0x10071000 EXPLODE 1(piece) ( type -- ) explode <piece> type <type>;
0x10072000 PLAY_SOUND 1(vol) ( sound -- result ) play-sound(<sound>, <vol>); — TA: Kingdoms 전용(.version 6). TA에서는 오류로 멈춥니다

설정 연산(0x108x)

옵코드 니모닉 인라인 스택 효과 BOS 문법
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>;

옵코드 인코딩

모든 옵코드 워드는 0x10CCSSFF 형태이며, 각 부분은 다음과 같습니다.

  • CC = 카테고리 바이트(애니메이션, 수학, 제어 흐름, …).
  • SS = 위에서 두 번째 바이트의 상위 니블에 있는 하위 명령 니블.
  • FF = 플래그 바이트.

게임은 word & 0x100FF000으로 분기하고 나머지 비트는 무시하므로, 0x10064001 같은 워드는 JUMP로 실행됩니다. 디스어셈블러는 이를 JUMP@0x10064001로 쓰고, 어셈블러는 이것을 읽어 같은 워드로 되돌립니다. PUSH와 POP은 하위 3비트에서 출처나 대상을 읽습니다. PUSH는 1(상수), 2(지역 변수), 4(정적 변수)를, POP은 2와 4를 받아들이며, 다른 값이면 스크립트가 오류로 멈춥니다.

어느 게임도 실행하지 않는 워드는 디스어셈블러에서 UNKNOWN_0x10xxxxxx로 표시됩니다(그리고 ta-unknown-opcode가 보고합니다). 실제 파일에서 이런 워드를 발견하면 COB 샘플과 함께 이슈를 올려 주세요.


부록 B — get_unit_value 포트 표

kbot의 디컴파일러가 현재 기호로 표시하는 포트 20개입니다. 실제 TA에는 약 80개가 있고, TA: Kingdoms는 마법 관련 수치를 위해 약 15개를 더 추가합니다. 현재 디컴파일러 출력에서 20번을 넘는 포트는 모두 숫자 리터럴로 표시됩니다.

포트 기호 이름 반환값 비고
1 ACTIVATION bool 유닛의 "활성화" 토글이 켜져 있는가(레이더, 재머, 방어 시설)?
2 STANDINGMOVEORDERS enum 0 = 위치 고수, 1 = 기동, 2 = 자유 이동.
3 STANDINGFIREORDERS enum 0 = 사격 중지, 1 = 응사, 2 = 자유 사격.
4 HEALTH 0–100 남은 MaxDamage의 백분율.
5 INBUILDSTANCE bool 유닛이 지금 나노 선반(nano-lathe)을 펼치고 있는가?
6 BUSY bool 유닛이 지금 명령을 수행 중인가?
7 PIECE_XZ packed 피스의 X/Z를 합친 값(인코딩됨). 투사체 조준에 쓰입니다.
8 PIECE_Y int 유닛 원점 기준 피스의 Y 좌표.
9 UNIT_XZ packed 유닛의 X/Z를 합친 값.
10 UNIT_Y int 유닛의 Y 좌표.
11 UNIT_HEIGHT int 지형 위 높이.
12 XZ_ATAN int XZ 인코딩된 두 값 사이의 atan2(포탑 조준용).
13 XZ_HYPOT int XZ 인코딩된 두 값 사이의 빗변 길이.
14 ATAN int 넣은 두 값의 atan2(y, x).
15 HYPOT int hypot(x, y).
16 GROUND_HEIGHT int 유닛 위치의 지형 고도.
17 BUILD_PERCENT_LEFT 0–100 SmokeUnit이 건설 진행도를 알기 위해 씁니다. 건설이 끝나면 0.
18 YARD_OPEN bool yard map이 있는 건물: 문이 열려 있는가?
19 BUGGER_OFF bool 유닛이 길을 비켜야 할 때 엔진이 설정합니다.
20 ARMORED bool 유닛의 장갑 태세가 켜져 있는가(Bulldog, Goliath 등)?

더 높은 포트에는 CURRENT_SPEED(29 — Cavedog Scriptor가 리터럴로 인라인해 버리는 것으로 유명합니다. 위의 주의할 점 참고), MAX_ID, UNIT_TEAM, UNIT_BUILD_PERCENT, BUILDER, UNIT_ALLIED 등이 있습니다. 전체 목록은 Cavedog의 내부 문서에 있으며, 공개된 것 중 가장 포괄적인 목록은 TAUniverse 포럼의 커뮤니티 위키에 있습니다.

TA: Kingdoms는 포트 범위를 넓힙니다. kbot이 인식하는 TAK 전용 포트(디컴파일러는 기호 이름으로 표시하고, 컴파일러는 입력으로 받아들임)는 Scriptor의 [UNITVLAUES] 테이블에서 가져왔습니다.

포트 기호 비고
21 WEAPON_AIM_ABORTED 무기의 조준 계산이 취소되면 설정됩니다.
22 WEAPON_READY 무기가 재장전되어 발사할 준비가 되었습니다.
23 WEAPON_LAUNCH_NOW 엔진이 무기를 발사했습니다.
26 FINISHED_DYING 사망 애니메이션이 끝났습니다.
27 ORIENTATION 유닛이 향한 방향.
28 IN_WATER 유닛이 물속에 있습니다.
29 CURRENT_SPEED 현재 이동 속도(TA와 공유 — Scriptor도 여기서 같은 번호를 씁니다).
31 MAGIC_DEATH 유닛이 마법 피해로 죽었습니다.
32 VETERAN_LEVEL 베테랑 등급.
34 ON_ROAD 유닛이 도로 타일 위에 있습니다.

부록 C — TA: Kingdoms 옵코드

Note

TA:K 전용 옵코드. 정식 출시판 TA의 .cob 파일에는 하나도 나오지 않습니다. DONT_SHADOW와 MISSION_COMMAND는 Scriptor(Switeck의 TAK 대응 컴파일러/디컴파일러)의 표준 니모닉을 씁니다. 수학 연산은 의미가 문서화되어 있지 않아 Scriptor가 ??와 ????로 표시하므로, kbot은 TAK_MATH_09 / TAK_MATH_0A / TAK_MATH_0B 자리 표시자를 유지합니다. 정식 출시판 TA: Kingdoms 스크립트는 비트 필드를 묶고 풀 때 이들을 각각 이항 연산자(값 두 개를 꺼내고 하나를 넣음)로 씁니다. TA 3.1c에는 PLAY_SOUND, MISSION_COMMAND, 수학 연산자를 처리할 핸들러가 없으며, DONT_SHADOW는 no-op으로 실행합니다.

옵코드 니모닉 인라인 정식 출시판 TAK의 사용 횟수 비고
0x1000A000 DONT_SHADOW 1(piece) 66 피스 하나의 그림자 드리우기를 끕니다. 애니메이션 카테고리에서 DONT_SHADE(0x1000E000) 옆에 있습니다 — kbot은 예전에 이것을 TAK_ANIM_0A라고 불렀습니다. 디스크상 형태는 다른 ANIM_* 연산과 같습니다: 4바이트 옵코드 + piece DWORD 하나. BOS 키워드: dont-shadow <piece>;.
0x10039000 TAK_MATH_09 0 264 수학 카테고리의 이항 연산자, 전체 4바이트: ( a b -- r ). BOS: __tak_math_09(a, b).
0x1003A000 TAK_MATH_0A 0 — 같은 형태. 일부 옵코드 표는 이를 BITWISE_NOT이라고 부르지만, TA의 비트 NOT은 0x10038000입니다. BOS: __tak_math_0a(a, b).
0x1003B000 TAK_MATH_0B 0 66 TAK_MATH_09와 같은 형태. BOS: __tak_math_0b(a, b).
0x10073000 MISSION_COMMAND 2(soundNameIdx, argCount) 3,885 엔진 명령 호출. 첫 번째 인라인 DWORD는 COB의 SoundNames 테이블에 대한 인덱스이고, 두 번째는 인수로 스택에서 꺼낼 값의 개수입니다. 이 옵코드는 결과 하나를 스택에 다시 넣습니다(보통 뒤따르는 POP_STACK으로 버리거나 POP_STATIC/POP_LOCAL로 저장). TAK 전용 옵코드 중 단연 가장 흔합니다. BOS 키워드: Mission-Command("name", args…) — Scriptor의 표준 문법과 같습니다.

인코딩(요약)

모든 COB 옵코드 워드는 0x10CCSSFF 형태이며 CC는 카테고리, SS는 하위 명령입니다. 이 TAK 옵코드들은 새 카테고리 바이트를 도입하지 않고, 기존 카테고리 안에서 이전에 쓰이지 않던 CC:SS 자리를 차지할 뿐입니다. DONT_SHADOW는 애니메이션 카테고리에서 DONT_CACHE/DONT_SHADE와 함께, MISSION_COMMAND는 효과 카테고리에서 EXPLODE, PLAY_SOUND와 함께 있습니다.

Tip

실제 바이트코드와 대조해 보세요.

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

레퍼런스 카탈로그의 실제 예

형식의 극단을 보여 주는 실제 유닛들입니다. 본문 속 예제가 아니라 실제 스크립트의 복잡도를 보려면 이것들을 열어 보세요.

  • CORKROG — Krogoth(Core) — 게임에서 가장 큰 COB 중 하나(약 25 KB, 스크립트 30개 이상, 복잡한 다중 피스 애니메이션).
  • ARMCOM — Arm Commander — 대표적인 "풍부한" 유닛: 걷기/조준/대기/건설 사이클, 여러 무기, D-gun 처리.
  • CORTRUCK — Construction Truck — 이 페이지의 해설에 쓴 거의 최소한의 COB(스크립트 3개: SmokeUnit, Create, Killed).
  • ARMSAM — Samson — 포탑 유닛의 좋은 예(turret1 조준 회전, 반동 애니메이션, wait-for-turn 동기화).

kbot 컨텍스트를 설정한 뒤에는 kbot cob decompile scripts/<name>.cob으로 어떤 스크립트든 로컬에서 디컴파일할 수 있습니다.


함께 보기

  • 3DO — COB 스크립트가 참조하는 피스 이름은 유닛의 .3do에 있습니다.
  • FBI — 유닛의 메타데이터 파일. 거기 있는 ObjectName이 COB와 3DO를 묶고, Sounds는 스크립트가 재생할 수 있는 오디오 뱅크를 정합니다.
  • kbot CLI의 cob lint 규칙 표.
  • 용어집 — piece, axis, signal mask, tick.