Animations
ANI/*.LIB
The intro, interludes, briefings and reports: BBHD clips of whole pictures and XOR changes, and the ASCR scripts that play them.
The intro, outro, interludes, mission briefings and mission reports are BBHD clips in ANI/ABCK000, ANI/AFUL000..005 and ANI/APER000, played by ASCR scripts in ANI/ASCR000. A clip stores numbered frames and nothing else. A script is a short text file that places clips on the screen, sets their speed and direction, and adds text, sound and palette fades.
A BBHD clip is a 70-byte header (BbhdHeader), a chain of frame records (BbfrRecord) and a 12-byte trailer (BbftTrailer). Each frame record is either a whole picture, the changes from the previous picture, or an instruction to show the previous picture again. Clips have no timing of their own: the same clip plays at different speeds in different scenes, because the ASCR script that plays it chooses the speed.
Clips in the shipped archives
| Archive | Clips | Sizes | Contents |
|---|---|---|---|
ANI/ABCK000.LIB | 46 | 120x64 | Backgrounds for the report window (HGD###), up to 111 frames. The archive also holds 24 placeholder entries whose text reads "Das ist nur ein Dummy File!!!" ("this is only a dummy file"). |
ANI/APER000.LIB | 46 | 28x16 to 120x64 | People and props for the report window (MOVI##): talking heads, caller cards, sender logos, weather symbols. |
ANI/AFUL000.LIB | 2 | 360x240 | The MILOP console opening and closing (KON_AUF, KON_PUSH). |
ANI/AFUL001.LIB | 11 | 320x200, 640x200 | Full-screen interludes. |
ANI/AFUL002.LIB | 4 | 320x200 | Full-screen interludes. |
ANI/AFUL003.LIB | 43 | 56x32 to 320x200 | Intro: the Blue Byte logo (ABB), the Battle Isle 2 logo (B201..B204), the planet scenes (DST1..DST4) and their parts. |
ANI/AFUL004.LIB | 7 | 24x32 to 320x396 | Full-screen scenes, including EXTRO2, and their parts. |
ANI/AFUL005.LIB | 4 | 72x40 to 84x22 | Small vehicles and a globe (PLANE, SHIP, TANK, WORLD), 100 frames each. |
ANI/AFUL010..012.LIB are not animations. Each is 48 MiB of filler with no archive table.
Clip Layout
BbhdHeader 70 bytes, at 0
BbfrRecord frame record 0, at 0x46
BbfrRecord frame record 1, at 0x46 + length of record 0
...
BbfrRecord last frame record, at last_record_offset
BbftTrailer 12 bytes
0xff one pad byte, only when the clip length is odd
The records form a chain in both directions. Each record stores its own length and the length of the record before it, and the header and trailer both point at the first and last record. A reader can therefore walk the clip forwards from the header or backwards from the trailer.
Every offset is relative to the start of the clip. The archive table does not record clip lengths; clip_length gives the length without the pad byte.
Chain
3 fields · 12 bytes
It is 0x46 (70) in every shipped clip, the end of this header.
databehaviour
The trailer repeats it.
data
An odd length is followed by one 0xff pad byte in the archive.
data
Frames
2 fields · 8 bytes
Anchor records count, so this can be more than the number of different pictures.
data
Shipped clips use 2 (every record stands alone) and 3 (records build on the previous picture, with anchors).
databehaviour
| bit | mask | name | meaning |
|---|---|---|---|
| 0 | 0x1 | dependent_frames | Records may be changes to, or repeats of, the previous picture, so they have to be applied in order. |
| 1 | 0x2 | anchored | Together with bit 0, the game expects anchor records at a fixed spacing of 11. Set alone in shipped clips whose records are all whole pictures or repeats. |
| 2–31 | unused | Never set in any record. |
Picture
5 fields · 10 bytes
When a clip is drawn with transparency, pixels of this index are skipped. The game applies it only to clips whose records stand alone (playback_flags bit 0 clear) and only when the play command's EFF is not 0; otherwise every pixel is drawn. In AFUL003:DST3 it is 238, the index that must be see-through for that layer to match the original intro.
behaviourdatarender
The game reads it together with transparent_index as one 16-bit word, treats the word 0xffff as "no transparency", and then compares pixels with the low byte only. Shipped values are 0, 80, 96, 144, 238, 243 and 255; no clip has 0xff in both bytes.
behaviourdata
datarender
datarender
ceil(width / 4) each.
A 120-pixel clip stores 1e 1e 1e 1e, a 320-pixel clip 50 50 50 50.
databehaviour
Buffers
2 fields · 8 bytes
The game adds 0x54 (84), keeps the largest value among the clips of a scene, and refuses to load a scene that needs more than it can reserve. The original tool wrote the size of the clip's largest whole-picture payload (anchors not counted) plus the same margin as picture_buffer_size; this holds for 153 of 163 clips, and the other 10 are off by up to about 700 bytes in either direction.
behaviourdata
packed_buffer_size.
It is width * height plus 50 when the clip's compressed pictures unpack to more than width * height bytes (134 clips), and plus 0 when they unpack to exactly that size (29 clips). This holds for every shipped clip.
behaviourdata
Reserved
1 field · 28 bytes
0xff in every shipped clip.
data
Frames
4 fields · 16 bytes
0 repeat, 1 whole picture, 2 compressed changes, 3 uncompressed changes.
See "Reference: frame types".
databehaviour
0.
Bit 14 marks an anchor, which repeats the previous record's number.
data
| bit | mask | name | meaning |
|---|---|---|---|
| 0–13 | 0x3fff | number | Picture number. Ordinary records count up by one. |
| 14 | 0x4000 | anchor | This record is a whole-picture copy of the picture just shown, with the same number. It adds no new picture. |
| 15 | 0x8000 | -never set in this field | Never set. |
0xff in every shipped record.
data
data
Chain
2 fields · 8 bytes
The next record starts at this record's offset plus this value.
data
record_length of the record before this one; 0 for the first record.
data
Chain
2 fields · 8 bytes
0x46 in every shipped clip.
data
data
Header
2 fields · 4 bytes
databehaviour
Whole pictures and compressed change streams are wrapped this way; it is the compression used throughout the game's archives. See "Reference: TPWM compression".
data
Reference: frame types
| Type | Name | Payload | Records |
|---|---|---|---|
0 | repeat | None. Show the previous picture again. | 267 |
1 | whole picture | A TPWM-compressed picture, width * height palette indexes, row by row. | 2,335 |
2 | changes | A TPWM-compressed change stream applied to the previous picture. | 2,904 |
3 | changes, uncompressed | The same change stream, stored without compression. | 40 |
A clip always starts with a whole picture. In clips with flags 3, an anchor (a whole picture with bit 14 set in frame_number) usually follows every 11 records, the first one at record 11. A few clips place the first anchor at record 2 or 4, or space later anchors more closely.
In forward play the game passes over anchors. Its frame counter counts records, but it skips an anchor's number, so the step that would land on an anchor goes straight to the next record and takes no longer than any other step. A decoder should therefore show one picture per record that is not an anchor; showing the anchors too holds every eleventh picture for an extra step. In all 63 clips with flags 3, the anchors are exactly the records that repeat the previous picture without being repeat records.
Some whole pictures decompress to more than width * height bytes. The extra bytes (20, 100 or 18 of them) are left over from the tool that made the files: zeros, or fragments of its memory such as the text SWEN. Readers should use the first width * height bytes and ignore the rest.
Reference: change stream
Types 2 and 3 store the difference from the previous picture. The stream is ordered the way VGA "Mode X" memory is laid out: by plane, then by column, then top to bottom. Each changed value is combined with the old pixel by exclusive-or (XOR), so applying the same change twice restores the original.
for plane in 0..3:
for x in plane, plane + 4, plane + 8, ... below width:
loop:
y_start = next byte # 1-based row; 0 ends this column
if y_start == 0: break
count = next byte as signed
y = y_start - 1
if count > 0: # count different values
repeat count times: pixel[x, y] ^= next byte; y += 1
if count < 0: # one value, -count times
value = next byte
repeat -count times: pixel[x, y] ^= value; y += 1
Columns with no changes are a single 0 byte. After the last column, 2,575 of the 2,904 compressed change streams carry 20 more bytes, 00 01 02 ... 13. They are not part of the stream and are ignored.
Reference: TPWM compression
The stream is a sequence of groups. Each group starts with a flag byte whose bits, highest first, describe the next eight items. A 0 bit is one literal byte, copied to the output. A 1 bit is a two-byte copy instruction a b:
count = (a & 0x0f) + 3 # 3..18 bytes
distance = b + (a >> 4) * 256 # 1..4095 bytes back
copy count bytes, one at a time, from output[length - distance]
The copy may overlap the bytes it is producing, which repeats a short pattern. Decompression stops when unpacked_length bytes have been written.
Reference: scene scripts
ANI/ASCR000.LIB holds 256 scripts, ASCR000 to ASCR255. They are plain DOS text, one command per line; there is no compiled form on the disc. 125 of them contain no commands. A script runs on a clock of 25 ticks per second (40 ms per tick), counted from the start of the scene.
initpal (15, 0, 1)
rem EFF,LNA,LNR,ENR,PMO,FRQ,BGN,DUR,END,PRI,STX,STY,DTX,DTY,WIL,WIT,WIW,WIH)
play ( 1, 32, 0, 43, 0, 8, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0,120, 64)
play ( 3, 31, 0, 69, 0, 12, 10,290, 2, 0, 0, 0, 0, 0, 0, 0,120, 64)
End ( 300 )
The rem lines are the authors' own comments; the argument names on this page come from them. Some lines carry stray text after the closing bracket, such as gto; it has no known effect.
| Command | Uses | Does |
|---|---|---|
play | 832 | Shows a clip as a layer. |
fx | 700 | Plays a sound effect from ENG/AFXS000. |
write | 314 | Shows text from ENG/ATXT000 in a box. |
initpal | 131 | Chooses the starting palette. |
End | 131 | Sets the scene length in ticks. |
BLEND | 89 | Fades part of the palette from one palette to another. |
clear | 42 | Clears a rectangle of the screen. |
fxstop | 24 | Stops sound effects at a tick. |
fmstart | 10 | Schedules an FM/audio request; the exact resource and audible result are not bound here. |
rem | Comment. remplay and remwrite are commented-out commands. |
The scripts mix upper and lower case (End, BLEND). The screen they draw on is 360x240 pixels.
play arguments
| Argument | Meaning |
|---|---|
EFF | How the layer is drawn over what is below it. Any value but 0 makes the clip's transparent_index see-through, for clips whose records stand alone. Its other effects are not fully understood. |
LNA | Archive family: 31 backgrounds (ABCK), 32 people (APER), 36 full-screen clips (AFUL). |
LNR | Archive number within the family, for example 3 for AFUL003. |
ENR | Which clip, counting from 0 in the archive's sorted entry list. It is not the number in the entry's name: names have gaps. |
PMO | Playback mode; see the next table. |
FRQ | Speed in pictures per second. FRQ=15 advances one record every 1.67 ticks. |
BGN | Tick at which the layer appears. |
DUR | Number of ticks it stays. |
END | What happens when DUR runs out. 0: the layer is no longer drawn. 1: the frame counter goes back to 0 and the layer stays on screen at its final position. 2: the layer stays on screen showing the picture it had reached, and stops advancing. 5 also appears; values 3 to 5 take another path that is not yet understood. |
PRI | Drawing order. |
STX, STY | Layer position inside its window. |
DTX, DTY | Movement per picture step, for layers that slide across the screen. |
WIL, WIT, WIW, WIH | The window: left, top, width and height. The layer is drawn relative to it and cut off at its edges. |
PMO | Plays | Used |
|---|---|---|
0 | Forwards once, then holds the last picture. | 213 |
1 | Backwards once, then holds the first picture. | 1 |
2 | Forwards and back again, holding the end picture for one step (0,1,2,3,3,2,1,0). | 1 |
3 | Forwards, repeating from the first picture. | 425 |
4 | Backwards, repeating from the last picture. | 0 |
5 | As 2, and also reverses DTX/DTY at each end. | 0 |
6 | Forwards, then repeats from the second picture. Later laps never show the first picture, nor an anchor that ends the clip. | 48 |
7 | Holds one picture. | 143 |
Other commands
write(TNA, TNR, TID, BGN, DUR, PRI, STY, SCO, PMA, PMB, PMC, ALG, BBH, WIL, WIT, WIW, WIH) shows text record TID of ENG/ATXT### (number TNR) in the window WIL..WIH from tick BGN for DUR ticks. STY=100 shows it at once and STY=101 types it out with a cursor. ALG sets the alignment: 0 left, 1 justified, 2 right, 3 centred. Fonts and colours are chosen by control codes inside the text record, not by write arguments.
fx(channel, bank, sound, BGN, volume, repeat) plays effect sound at tick BGN. A negative BGN ties the sound to the typed text instead of a tick; the scripts use -1 for the cursor sound and -2 for the line-return sound.
initpal(selector, archive, entry) and BLEND(selector, fromArchive, fromEntry, selector, toArchive, toEntry, low, high, begin, end) name palettes in ANI/APAL###. A fade changes only the colours low..high, starts from the palette it names (not from what is on screen) and lasts at most 127 ticks. A fade changes colours only; it never creates in-between pictures.
clear(begin, colour, left, top, width, height) clears a rectangle at tick begin. The second argument is 0 in every script and is probably the fill colour. End(ticks) ends the scene.
Still to investigate
Every field marked unknown or partial above, and every bit that is set without a name or named without ever being set.
- playback_flags◐How records depend on each other. BbhdHeader
- transparent_index_high◐No visible effect. BbhdHeader
- frame_number●Bit 15 (-) is named but never set. BbfrRecord