Battle Isle 2

Fight 3D resources

FGT/*.LIB, FGT/*.DAT

Everything that draws a close-combat scene: 3D meshes, scenery lists, palettes, movement and camera tables, and the sine table.

Close-combat scenes in Battle Isle 2 are drawn with small 3D meshes rather than sprites. Everything that draws them lives in the FGT/ directory: the meshes (three archives sharing a 3DOB magic), the scenery and ground lists, the palettes, the backdrops, the movement and camera tables, a sine/tangent table and the sound banks. This page describes each in turn.

Status: resource layouts and documented ordinary-fight playback rules recovered. All 520 mesh records validate against the decoded header, vertices, polygons and moving-part layout. The scene, motion, raster, clock and audio source rules are documented below. Native commands now establish ordinary cross-fight phase retention. No identified algorithm gap remains for the documented parameterized paths. One particle integration input producer remains unbound, as described below; native visual, audio and cadence conformance is also incomplete.

ObjectHeader66 bytes3DOBchar[4]vertex_offsetu16slot_offsetu16face_offsetu16trailing_offsetu16reserved_0cu16[5]vertex_count_minus_oneu16face_count_minus_oneu16part_countu16lead_1cu8[8]world_positioni16[3]object_rotationi16[3]lead_30u8[18]Polygonsvariable sizePolygonRecord × face_countvariable sizeLayout2 BVertices4 BRuntime12 BColour5 BDrawing3 BVertices6 × vertex_count bytesVertex × vertex_count6 bytes eachPosition6 BFace normals6 × face_count bytesFaceNormal × face_count6 bytes eachNormal6 BParts32 × part_count + listsPartRecord × part_count32 bytes eachGeometry8 BPivot6 BRuntime12 BReserved6 Bvertex listsu16[]Vertex slots10 × vertex_count bytes010 bytes each

Each box is one table of a 3DOB object, in file order. The polygons start right after the header; the other tables are found by the four offsets in the header, and each line runs from an offset to the table it locates. A stack stands for the records of a table, with its count in the label. Cut blocks are not drawn to scale. Dashed blocks hold the same value in every object.

Archives

ArchiveRecordsContents
FGT/UN3D000.LIB240units (UNIT###), animations (ANIT###)
FGT/LAND000.LIB288terrain objects (LAND###)
FGT/LAND300.LIB288terrain objects
FGT/PAL000.LIB41the fight palettes the meshes are coloured from
FGT/LAYR000.LIB, LAYR300.LIB96 each3DLD scenery layer lists
FGT/GRND000.LIB, GRND300.LIB24 each3DLD ground lists
FGT/BACK000.LIB20the raster horizon backdrops and their moons - not meshes; see Where the mountains go

520 of the entries carry the 3DOB magic; the remaining 296 are 8-byte LEER__ (German *leer*, "empty") placeholders for unused slots.

FGT/SCHOSS.DAT (German *Schuss*, "shot") is a twelfth archive in a container of its own, and it holds the ammunition. Its own layout is simple: a u32 at 0x00 gives the offset of the directory, TCT and an entry count follow at 0x04, and the directory is 40 records of an 8-byte name and a u32 offset. The 40 slots are ten AMMO### groups in A/B/C/S form, of which 11 hold real 3DOB objects - the ten A models plus AMMO005S - and the other 29 are LEER__.

They obey the object layout above (face_offset - vertex_offset == vertex_count * 6 and so on) and none has parts:

ObjectSizeReads as
AMMO001A2 vertices, 1 two-vertex face, steela tracer: a bare 320-unit line
AMMO002A7 vertices, 8 faces, steel and olivea shell, 420 units long and 30 wide
AMMO005A9 vertices, 2 faces, all sky, y = 0 throughouta flat water splash
AMMO006A16 vertices, 10 faces, steel and golda 110-unit ball with gold octagon caps
AMMO050A12 vertices, 8 faces, entirely golda 120-unit ball of flame - a muzzle or impact flash

The rest of FGT/ is not 3D. FGTEXPLO.LIB and FGTSHOOT.LIB are Creative Voice (.VOC) samples, named by where the explosion happens (LAND, WATER, UWATER, AIR) and by weapon type (MG, KAN, ARI, RAK, BOMBE, WBOMB, TORP, GTORP, with LEER in the unused slots); FGTWIN.LIB and FGTLOS.LIB are eight XMIDI stings each for winning and losing a fight.

Object layout

All multi-byte integers are little-endian.

66 bytes13 fields● 10 ◐ 3 ○ 0
0x00char[4]tag ●Literal 3DOB (0x424f4433).

Tables

4 fields · 8 bytes
0x04u16vertex_offset ●byte offset of the vertex table
0x06u16slot_offset ●byte offset of the runtime vertex slots; equals trailing_offset on the 410 objects that have no parts
0x08u16face_offset ●byte offset of the face-normal table
0x0au16trailing_offset ●byte offset of the part table

Counts

3 fields · 6 bytes
0x16u16vertex_count_minus_one ●vertex count is this + 1
0x18u16face_count_minus_one ●face count is this + 1
0x1au16part_count ●first word of the 40-byte object lead; see Moving parts

Runtime

4 fields · 38 bytes
0x1cu8[8]lead_1c ◐Zero on disk; runtime role not established here.
0x24i16[3]world_position ●Zero on disk; transform entry stores caller XYZ here.
0x2ai16[3]object_rotation ●Zero on disk; transform entry stores caller XYZ angles here.
0x30u8[18]lead_30 ◐Zero on disk; runtime role not established here.
slotworld_positionobject_rotation
A0x240x2a
B0x260x2c
C0x280x2e
Where each slot's values sit: slot k is element k of every array above.

Reserved

1 field · 10 bytes
0x0cu16[5]reserved_0c ◐0 across the corpus
6 bytes3 fields● 3 ◐ 0 ○ 0
0x00
Point at the bar to name a field; select it to jump to its row.
knownpartly knownunknown: investigatenamed, never setalways one valueunused bits

Position

3 fields · 6 bytes
0x00i16x ●model space
0x02i16y ●+Y is down, so up is -Y
0x04i16z ●model space
6 bytes3 fields● 3 ◐ 0 ○ 0
0x00
Point at the bar to name a field; select it to jump to its row.
knownpartly knownunknown: investigatenamed, never setalways one valueunused bits

Normal

3 fields · 6 bytes
0x00i16x ●one per polygon; a unit normal in 14-bit fixed point (16384 = 1.0)
0x02i16y ●as x
0x04i16z ●as x
variable size14 fields● 9 ◐ 5 ○ 0

Layout

1 field · 2 bytes
0x00u16record_size ●total bytes of this record; also 26 + 2*vertex_count - the fixed fields run +0x00..+0x19, then one word per vertex.

Vertices

3 fields · 4 bytes
0x02u16first_vertex_ptr ●equals the first entry of vertex_ptrs
0x0cu16vertex_count_minus_one ●polygon has this + 1 vertices
tailu16[vertex_count]vertex_ptrs ●the last vertex_count words

Runtime

4 fields · 12 bytes
0x04i16[3]normal_scratch ●Zero on disk; normal XYZ is copied here and rotated at runtime.
0x0au8[2]containing_part_ids ●Cleared per draw; tagged faces record up to two containing part numbers in traversal order for optional part commands.
0x16i16light_term_scratch ●Zero on disk; preparation writes the pre-camera rotated normal term, or the grouped/disabled-normal replacement.
0x18u8[2]anchor_indices ◐Initialization zeros this word; the conditional anchor pass writes two counter bytes consumed as signed anchor indices.
slotcontaining_part_idsanchor_indices
A0x0a0x18
B0x0b0x19
Where each slot's values sit: slot k is element k of every array above.

Colour

3 fields · 5 bytes
0x0eu16shade ●0, 32, 64, 96, 128, 160 - the polygon's colour: a base index into the fight palette, see Colour
0x13i8lighting_mode ◐Selects ordinary, ambient-only, direct-bias and special normal/material paths.
0x14i16bias ●-8..+8; ordinary shade step, or anchor key/effect-model selector in the special material path.

Drawing

3 fields · 3 bytes
0x10u8raster_mode ◐Selects raster dispatch; mode 1 also participates in anchor preparation.
0x11i8face_group_control ◐Positive reuses the prior lighting term, negative sets it to zero; also controls face-list traversal and culling.
0x12u8normal_control ◐Nonzero bypasses ordinary normal rotation and takes the zero lighting-term path; also bypasses one backface test.
32 bytes10 fields● 9 ◐ 1 ○ 0
0x00
Point at the bar to name a field; select it to jump to its row.
knownpartly knownunknown: investigatenamed, never setalways one valueunused bits

Geometry

4 fields · 8 bytes
0x00u16polygon_ptr ●file offset of the part's first polygon record
0x02u16polygon_count_minus_one ●the part owns this + 1 consecutive polygons
0x04u16vertex_list_ptr ●file offset of the part's vertex-index list
0x06u16vertex_count_minus_one ●the list holds this + 1 entries

Pivot

3 fields · 6 bytes
0x08i16pivot_x ●the axis the part turns about, in model space
0x0ai16pivot_y ●up is -Y, as everywhere else in the format
0x0ci16pivot_z ●

Runtime

2 fields · 12 bytes
0x0ei16[3]translation ●0 on disk; runtime X/Y/Z translation added after pivot rotation
0x14i16[3]angles ●runtime X/Y/Z angles; applied in Z, X, Y order; 0 on disk.
slottranslationangles
A0x0e0x14
B0x100x16
C0x120x18
Where each slot's values sit: slot k is element k of every array above.

Reserved

1 field · 6 bytes
0x1au8[6]reserved_1a ◐0 on disk; runtime role unresolved
variable size7 fields● 6 ◐ 1 ○ 0
0x00char[4]tag ●Literal 3DLD.

Objects

1 field · 12 bytes
0x04u16[6]object_ids ●ids of the LAND objects this record may place; 0xffff/0xfffe mark unused slots

Colour

2 fields · 4 bytes
0x1cu16ground_palette_base ●GRND loader passes this palette base to the continuous-floor renderer.
0x1ei16ground_shade_adjustment ●Added to renderer ambient for that side of the continuous ground.

Placements

2 fields · 2 bytes
0x20u16count ●placements
0x22..Placement[]placements ●16 bytes each

Reserved

1 field · 12 bytes
0x10u8[12]padding ◐Padding or leftover source filename bytes; not the colour words that follow.
16 bytes6 fields● 6 ◐ 0 ○ 0
0x00
Point at the bar to name a field; select it to jump to its row.
knownpartly knownunknown: investigatenamed, never setalways one valueunused bits

Position

4 fields · 8 bytes
0x00u16slot ●index into object_ids
0x04i16x ●always a multiple of 1000
0x06i16z ●always a multiple of 1000
0x08i16y ●Zero on disk; copied as vertical position at submission.

Drawing

1 field · 2 bytes
0x02i16draw_control ●0, -1 or -2 in these files; copied unchanged to staged controls.

Rotation

1 field · 6 bytes
0x0ai16[3]rotations ●Zero on disk; copied as object X/Y/Z rotations.

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.

  • lead_1c◐Zero on disk; runtime role not established here. ObjectHeader
  • lead_30◐Zero on disk; runtime role not established here. ObjectHeader
  • raster_mode◐Selects raster dispatch; mode 1 also participates in anchor preparation. PolygonRecord
  • face_group_control◐Positive reuses the prior lighting term, negative sets it to zero; also controls face-list traversal and culling. PolygonRecord
  • normal_control◐Nonzero bypasses ordinary normal rotation and takes the zero lighting-term path; also bypasses one backface test. PolygonRecord
  • lighting_mode◐Selects ordinary, ambient-only, direct-bias and special normal/material paths. PolygonRecord
  • anchor_indices◐Initialization zeros this word; the conditional anchor pass writes two counter bytes consumed as signed anchor indices. PolygonRecord