🛡️ TA Archive

COB-Exact Unit Animation — What the Bytecode Really Says

Knowledge Base · Render Pipeline

COB-Exact Unit Animation

Every unit animation on TA Archive is a 1:1 replay of the unit's original scripts/*.COB bytecode — timings, piece motions and shot cadences taken from the disassembly, nothing invented. This page collects the verified number formats, the transform conventions and the pitfalls we proved against the game, with the hard measurements behind each claim.

The principle

A unit's COB script is the same bytecode Total Annihilation executes: AimPrimary, FirePrimary, activatescr/RequestState, MotionControl. We disassemble it (_cob_dis.py), then replay its turn/move/spin/sleep statements on the original .3do mesh with a software renderer (_cob_anim.py). If a number looks odd — Big Bertha's 2°/s gun elevation, a 10-second ready hold, a 3.2 elmo foot snap — it is odd in the game.

Verified number formats

ValueFormatEvidence
AnglesBAM int32, 65536 = 360° (so 16450 = 90.363°)ARMAMB lids fold exactly 90.363°; annotated constants match human-round degrees (5461 = 30°, 1820 = 10°)
Paths / offsets3DO units 1:1 (1 elmo = 163840)With 1:1 scaling, six independent ARMAMB parts land exactly at ground level in the folded pose (hinge deviations < 2%). The old “divide by 5” calibration was wrong
Turn/move speedsBAM/s and elmo/s respectivelyDurations computed as Δ/speed match the script's own sleep values (e.g. Rocko's 3 elmo return at 3 elmo/s = the 800 ms the script waits)

Transform and sign conventions

The model chain is parent · T(offset + move) · R(turn), with moves in parent space. COB coordinates reach our camera frame through a 180° rotation about y (x and z flip, y is kept) — turns and moves about/along x and z therefore flip sign, y does not. That rule won every A/B test it was put into — the EMP covers sink through the terrain without it — with one hard exception:

The Eraser fork (verified against the game): the splay of its two prongs (ltong/rtong y, ±119°) had to be negated against the raw COB values to make the prongs swing forward like the game. Measurement: prong-tip z is +181k (backward) with raw signs and −218k/−407k (forward) with negated ones. Lesson: the sense of rotations about y is not settled by the sign rule. Turret yaw direction was never verified against a reference — verify every y-rotation direction by a forward/backward measurement (piece-tip z) or a game reference before trusting it.

Effects with no geometry (honest gaps)

  • Muzzle flares can be empty SFX anchors (1 vertex, 0 primitives — Goliath's flare, Flash's flare1/2). The flash is an engine effect at that anchor and cannot be rendered. Real flare pieces carry 3 primitives (Stumpy's flare, Peewee's rfire). Check the primitive count before wiring flash windows.
  • Ship wakes (wake1/wake2) and nano beams are SFX emitters re-armed every 300 ms — no mesh, not renderable. Missiles, bombs and tracers are engine-spawned the same way.
  • Flare colours follow the unit's palette. CORE flashes come out red/orange; a yellow pixel test misses them. Verify flash visibility by triangle count (a flare piece adds exactly 3), never by colour.

Timing gotchas

  • Weapon TDF reloadtime is the engine's shot cadence (EMG 0.4 s, Heavy Cannon 1.5 s, Rockets 4 s). COB Reload/SetMaxReloadTime gates aiming — a different clock (the Crusader's restore delay is literally 2 × the longest reload).
  • KBot gaits are stop-motion. MotionControl carries 12–20 turn-now poses held 30–150 ms (Peewee 740 ms/12 poses, Eraser 1040 ms/16) — we replay them as hold segments, which is exactly how the original interpreter flashes through them.
  • Alternating barrels are a static-variable toggle in FirePrimary (Bulldog starts with barrel1, Crusader with barrel2 — static0=1 at Create). The pattern is only visible in the bytecode.
  • Opcode 0x10055000 is a comparison (==), not bitwise and — under an “and” reading the Crusader's alternating fire would be dead code.
  • Engine hooks stay out of the timeline: RockUnit/HitByWeapon (hull rock), MoveRate2 (bank), StartMoving wake loops — they take their arguments from the engine, so any values would be invented.

Why a walk can look “backwards”

In-place gait loops make the planted foot creep ~8 elmo per cycle backwards while the body does not advance — a moonwalk-reading the eye happily produces. Measured on the Rocko: the swing foot travels fast towards the weapon side (forward, −z), the stance foot slides back slowly — textbook forward walking. Numbers beat the eye here; we keep the faithful in-place loop.

Pipeline

_cob_dis.py (verified opcode table) → _cob_anim.py (channels, terrain clip, fixed camera envelope, walk_keys gait parser) → <uid>_anim.py (the unit's timeline, straight from its disassembly) → GIF + numbered frames → per-unit control page with the phase list and data/units_timelines.json for the unit dialog. 18 units are live this way: ARMAMB, Annihilator, ARMEMP, Big Bertha, Bulldog, Conqueror, Crusader, Eraser, Peewee, Thud, Rocko, A.K., Stumpy, Goliath, Flash, Thunder, Freedom Fighter and the Construction Vehicle.