How real mods write BOS
Almost every unit archive ships only compiled .cob. The Secret Forces ships the source too β 24 units, each with its .bos script next to the compiled bytecode. That makes it possible to compare what modders actually wrote against what the compiler produced, against Cavedog's own standard headers, and against our byte-level decode of a retail unit. This article reads that material.
TSF Units.UFO with 24 units: each has .3do, compiled .cob, uncompiled .bos, a unit .tdf and the shared tsf weapons.tdf. One unit even keeps a TSFTL_ORIGINAL.bos next to its edited version. Reference points: Cavedog's STDTANK.H/EXPTYPE.H/SFXTYPE.H headers (shipped with the community compiler), and the AMPH reconstruction article.1 Β· The anatomy of a real unit script
Here is the opening of tsfat.bos (an armored tank β the same script class as retail AMPH), exactly as the modder left it:
#define TA // This is a TA script#include “sfxtype.h” #include “exptype.h”
piece base, body, track1, track2, turret, barrel, flare1;
static-var restore_delay, poo;
// Signal definitions #define SIG_AIM 2
RockUnit(anglex, anglez) { turn base to x-axis anglex speed <50.010989>; turn base to z-axis anglez speed <50.010989>; wait-for-turn base around z-axis; wait-for-turn base around x-axis; turn base to z-axis <0.000000> speed <20.000000>; turn base to x-axis <0.000000> speed <20.000000>; }
Three things are immediately different from how we reconstructed retail AMPH:
- Angle literals:
<50.010989>β degrees in angle brackets. The compiler converts them to BAM (the game's 65536-units-per-turn angles) at compile time.<50.010989>compiles to 9101 in the shipped COB β the same ~9100 range as retail AMPH'sRockUnitspeed (9100). Distances use[4.2]-style brackets (elmo), and everything else is raw. - Named signals:
#define SIG_AIM 2instead of a baresignal 2. Same bytecode. - The human residue:
static-var restore_delay, poo;β the second static is literally namedpoo. Real code has leftovers.
And the aiming core is the textbook pattern verbatim:
AimPrimary(heading, pitch)
{
signal SIG_AIM;
set-signal-mask SIG_AIM;
turn turret to y-axis heading speed <35.005495>;
turn barrel to x-axis <0.000000> - pitch speed <15.000000>;
wait-for-turn turret around y-axis;
wait-for-turn barrel around x-axis;
start-script RestoreAfterDelay();
...
}The <0.0> - pitch inversion, the signal dance, the restore thread β this is Cavedog's STDTANK.H AimPrimary with the same two turn speeds. That is no coincidence: the constants match our retail decode exactly. Retail AMPH's compiled aim speeds are 6370 and 2730 BAM; here the source says <35.005495> and <15.000000> degrees. Convert: 35.005495Β° Γ 65536/360 = 6372.5, and 15Β° Γ 65536/360 = 2730.7 β the shipped COB carries 6371 and 2730. The modder took the standard header's values, and the conversion factor 65536/360 that we derived from bytecode in the AMPH article is confirmed by independent source code.
2 Β· The three patterns in the wild
The aim/fire vehicle (tsfat, tsffav, tsflt)
RockUnit/HitByWeapon β Create (hide flare) β SetMaxReloadTime/RestoreAfterDelay β Aim*/Fire*/Query*/AimFrom* β Killed ladder. This is the AMPH class β and the mod's versions are near-identical to retail, differing in speed constants and names.
The deploy/state-machine factory (tsfcv, tsfvp, tsfsy, tsfasf)
These carry Cavedog's state-machine idiom, which the retail scripts also use: InitState, RequestState, Go/Stop, plus activatescr/deactivatescr or OpenYard/CloseYard pairs. The busy-flag + wanted-state pattern (while (state != wanted) sleep) is written out longhand in every one of them β the code is copied between mods, exactly like real software.
The walking commander (tsfcom)
21 scripts, ~107 motion operations: walk/walklegs/MotionControl for the gait, aim/fire on top, and a factory hook. The gait is the same while (moving) loop family as retail walkers, split over two scripts (transition stride + loop runner) β the pattern our animation pipeline learned to recognise the hard way.
3 Β· The modern dialect: detecthack()
Then there is this, at the top of tsfcom.bos β an anti-hack probe that has nothing to do with animating a mech:
detecthack()
{
sleep rand(500,5000);
var unit_ID, numbcared;
var max, min;
max = get MAX_ID;
min = get MIN_ID;
var myteam;
myteam = get UNIT_TEAM(get MY_ID);
numbcared=0;
while(1)
{
numbcared=0;
for (unit_ID = min; unit_ID <= max; ++unit_ID)
{
if (get UNIT_XZ(unit_ID) == get PIECE_XZ(base))
{
if (get UNIT_HEIGHT(unit_ID) == [4.2])
{
++numbcared;
}
}
}
...This is a different BOS language level than the Cavedog scripts: local var declarations, for/while(1) loops, ++, rand(a,b), and get with parameters (engine values per unit: UNIT_TEAM(unit), UNIT_XZ(unit), PIECE_XZ(piece)). It scans every unit on the map and checks whether something with the commander's exact position and a specific height exists β a map-hack/game-hack detector, likely counted and reported via a unit value.
It compiles because the modder used a compiler with the extended syntax (the community ggs/BASM line advertises "inlineable functions", AND/OR/XOR keywords and Scriptor compatibility). The bytecode side is the same stack machine: rand is 0x10041000, and the parameterised get is the getuv family (0x10042000) our opcode table lists next to plain get.
MIN_ID = 69, MAX_ID = 70, MY_ID = 71, UNIT_TEAM = 72, UNIT_BUILD_PERCENT_LEFT = 73, UNIT_ALLIED = 74, UNIT_IS_ON_THIS_COMP = 75, VETERAN_LEVEL = 32. Together with the EXPTYPE.H/SFXTYPE.H includes and the registry tables in the AMPH article, this is now the third independent piece of Cavedog's constant documentation recovered from living code.4 Β· Round-trip on third-party code
The strongest check for any decode is recompilation. We compiled the mod's own tsfat.bos with the community toolchain (BOS to BASM v0.82 + BASM 0.8.1.1, under Wine) and compared the result against the tsfat.cob the mod itself shipped:
150 ms flash sleeps. The only differences: six turn-speed constants, each appearing twice (12 words), off by 1β6 BAM (β€ 0.03Β°):| Source literal | Shipped COB | Recompiled | Ξ |
|---|---|---|---|
<50.010989> | 9101 | 9104 | 3 |
<20.000000> | 3640 | 3641 | 1 |
<105.021978> | 19113 | 19119 | 6 |
<30.005495> | 5461 | 5462 | 1 |
<35.005495> | 6371 | 6373 | 2 |
<15.000000> | 2730 | 2731 | 1 |
The shipped compiler converted degrees to BAM with slightly different rounding (or truncated an intermediate value) than ggs' does. Everything else β the whole 430-word structure including the four-tier Killed ladder β matches. The shipped COB carries no compiler signature blob at all (retail COBBLER builds embed a "Build by COBBLER" string; this one is silent), and the recompiled file differs in total length by only 3 words at the tail of the death script.
Meaning: our opcode semantics, operand order, and literal conversions are not just self-consistent β third-party source, third-party bytecode and our decoder all agree. This is the same conclusion as the AMPH round-trip (retail code, retail COB), now from a second, independent codebase.
5 Β· What mod code teaches decoders
- The literal forms are part of the language:
<deg>for angles (compiled Γ 65536/360),[elmo]for distances (Γ 65536), bare numbers for raw values and milliseconds. A decoder that treats all three as "a number" will be off by 182Γ or 65536Γ on some scripts. - Patterns travel by copy-paste: aim/fire/restore, the state machine, the gait loop β recognising the family from 3 script names beats reading 200 lines of bytecode. That is exactly the routing rule our animation pipeline uses.
- Sources can disagree with shipped COBs by tiny constants. A 1β6 BAM delta is a compiler rounding artifact, not a decode error β when reconciling source against bytecode, compare structure first and treat angle constants within Β±10 BAM as equal.
- Modern dialect scripts exist in the wild (
var,for,rand, parameterisedget). A toolchain that only speaks the retail dialect will reject real mods; the community compilers speak both. - Not every COB carries a compiler signature. The presence of "Build by COBBLER" is a fact about that compiler, not about the format.
6 Β· The material, browsed
| Unit | Scripts | Class |
|---|---|---|
tsfcom | 21 (incl. detecthack, walk, walklegs) | walker commander β gait + weapons + factory |
tsfat, tsffav, tsflt | 12β13 | aim/fire vehicles (the AMPH class) |
tsfcv, tsfvp, tsfsy, tsfasf | 17β19 | deploy/factory state machines |
tsfacs, tsfcs, tsfct | 9β14 | construction / build-stance units |
tsfeg, tsfrt, tsfsc | 4β6 | minimal (feature-like, nearly no motion) |
tsfmakr, tsfmex, tsfwt | 10β11 | economy with setspeed/setdirection engine hooks |
others (tsfnano, tsfts, tsftl+TSFTL_ORIGINAL) | 14β17 | yards, specials |
All 24 .bos sources, the compiled .cob files and tsf weapons.tdf are extracted and preserved in our working archive; the mod zip itself is on the downloads page. Two of these units are already live as COB-exact previews: TSFAT and TSFCOM.
7 Β· Credits
The Secret Forces team (the mod, its scripts and models) β rights remain with them; preserved here for study. Cavedog Entertainment β BOS, the standard headers, the engine. ggs & Mafia β the compiler toolchain whose registries and round-trip made this article testable. Related: the retail AMPH decode and the toolchain archive.