FF7/Playstation battle stage format
Contents
Playstation battle stage format
The entire file is compressed using LZS format and is decompressed into the playstation user memory for a battle. The file consists of a header section, metadata, multiple 3D geometry sections for the ground plane and environment, and a texture image in TIM format. All integers short or long are little endian based.
Battle stage header
The header section is a sequence of little endian 32 bit unsigned integers. The first integer (entitled SectionCount) contains the number of pointers offset from the beginning of the file. The remaining integers are SectionCount number of pointers.
| Offset |
Size |
Description |
| 000000 |
4 bytes long |
Number of Sections (N) |
| 000004 |
N*4 bytes |
Array of section offsets from file start |
| (N+1)*4 |
8 bytes |
Section 0: Metadata flags |
| ... |
Varies |
Sections 1 to N-2: 3D Geometry (ground plane, sky, environment objects) |
| ... |
Varies |
Section N-1: Tim image information (8bpp, multiple CLUTs) |
Note: Battle stages can also contain animations (e.g. Corel Train Battle - STAGE71), but their data structure is currently unknown. The above structure is valid for stages that do not contain any animations.
Section 0: Metadata
8 bytes containing stage configuration flags.
| Offset |
Size |
Description |
| 000000 |
3 bytes |
Flags (purpose TBD) |
| 000003 |
1 byte |
Type of 3D mesh |
| 000004 |
4 bytes long |
Reserved (always 0) |
Mesh Type Values:
| Type |
Description |
Example Fields |
| 0 |
Mesh with horizontal scrolling parts |
Field 47 - Corel Train Battle |
| 1 |
Normal static mesh |
- |
| 2 |
Mesh with vertical scrolling parts |
Field 12 - Shinra Elevators |
| 3 |
Mesh with lifestream |
Field 4E - Final Battle - Sephiroth |
| 4 |
Mesh with rotating parts |
Field 39 - Safer Battle |
| 5 |
Normal static mesh (same as type 1) |
Field 01, 44, 45 - Bizarro Battles |
Sections 1 to N-2: 3D Geometry
All geometry sections (including the ground plane at section 1) use the same unified format. This matches the Battle Model Format where polygon data uses explicit PolyCount structures.
Geometry section structure
| Offset |
Size |
Description |
| 000000 |
4 bytes long |
Vertex data size (in bytes) |
| 000004 |
Vertex data size bytes |
Vertex pool for geometry section |
| ... |
2 bytes (word) |
Number of triangles |
| ... |
2 bytes (word) |
Texture page (tpage) |
| ... |
N * 16 bytes |
Textured Triangles if N = 0 these occupy no space |
| ... |
2 bytes (word) |
Number of quads |
| ... |
2 bytes (word) |
Texture page (tpage) |
| ... |
N * 20 bytes |
Textured Quads if N = 0 these occupy no space |
| ... |
8 bytes |
Unknown/padding. 00's most of the time but STAGE09 has some data here and some unknown bytes immediately after. |
Note: The texture page X for the entire section comes from the first PolyCount's tpage field (triangle PolyCount), even when triangle count is 0. Use tpage & 0x0F to extract the texture page X base.
Data structures
typedef struct
{
uint16 Count;
uint16 TexPage;
} PolyCount;
typedef struct
{
int16 X;
int16 Z;
int16 Y;
int16 Unused;
} Vertex;
typedef struct
{
uint16 A;
uint16 B;
uint16 C;
uint16 D;
} PolyIndices;
typedef struct
{
uint16 VertexA;
uint16 VertexB;
uint16 VertexC;
uint16 Flags;
uint8 U0, V0;
uint16 CLUT;
uint8 U1, V1;
uint8 U2, V2;
} TexTriangle;
typedef struct
{
PolyIndices Vertices;
uint8 U0, V0;
uint16 CLUT;
uint8 U1, V1;
uint8 U2, V2;
uint8 U3, V3;
uint16 Flags;
} TexQuad;
Vertex Structure
| Offset |
Size |
Description |
| 000000 |
2 bytes (signed) |
X coordinate |
| 000002 |
2 bytes (signed) |
Z coordinate (height/depth, 0 for ground plane) |
| 000004 |
2 bytes (signed) |
Y coordinate |
| 000006 |
2 bytes (word) |
Padding (always 0) |
Ground plane vertices typically have Z=0 (flat surface). Typical coordinate range: -6000 to +6000 units.
Textured Triangle Structure (16 bytes)
| Offset |
Size |
Description |
| 000000 |
6 bytes |
3× vertex byte offsets (uint16, divide by 8 for vertex index) |
| 000006 |
2 bytes (word) |
Unknown/flags |
| 000008 |
1 byte |
U0 coordinate |
| 000009 |
1 byte |
V0 coordinate |
| 00000A |
2 bytes (word) |
CLUT attribute (palette selector) |
| 00000C |
1 byte |
U1 coordinate |
| 00000D |
1 byte |
V1 coordinate |
| 00000E |
1 byte |
U2 coordinate |
| 00000F |
1 byte |
V2 coordinate |
Textured Quad Structure (20 bytes)
| Offset |
Size |
Description |
| 000000 |
8 bytes |
4× vertex byte offsets (uint16, divide by 8 for vertex index) |
| 000008 |
1 byte |
U0 coordinate |
| 000009 |
1 byte |
V0 coordinate |
| 00000A |
2 bytes (word) |
CLUT attribute (palette selector) |
| 00000C |
1 byte |
U1 coordinate |
| 00000D |
1 byte |
V1 coordinate |
| 00000E |
1 byte |
U2 coordinate |
| 00000F |
1 byte |
V2 coordinate |
| 000010 |
1 byte |
U3 coordinate |
| 000011 |
1 byte |
V3 coordinate |
| 000012 |
2 bytes (word) |
Flags (typically 0x007F) |
Each quad vertex has its own UV coordinates: (U0,V0), (U1,V1), (U2,V2), (U3,V3).
Common section contents
| Section |
Typical Content |
Notes |
| 1 |
Ground plane |
Usually quads only, Z=0 |
| 2 |
Sky dome (upper) |
Often triangles |
| 3 |
Sky ring (middle) |
Horizon band |
| 4 |
Sky ring (lower) |
Near-ground atmosphere |
| 5+ |
Stage objects |
Buildings, structures, props |
CLUT attribute
Standard PSX CLUT format encoding palette row:
- Bits 0-5: CLUT X position ÷ 16 (typically 0x14 = 320)
- Bits 6-14: CLUT Y position (504 + palette_index)
- Formula:
palette_index = ((clut_word >> 6) & 0x1FF) - 504 - Example: 0x7E14 = palette 0, 0x7E54 = palette 1, 0x7E94 = palette 2
Texture page system
The texture atlas is divided into 128-pixel-wide pages for 8bpp images:
- Page width: 128 pixels (64 VRAM units)
- Encoding: Low nibble of PolyCount tpage field
- Offset calculation:
texture_x_offset = (tpage_x - base_tpage_x) × 128 - Base tpage X = TIM image VRAM X ÷ 64
Example (STAGE00):
- Ground plane: tpage 6 → offset 0px (left half)
- Sky sections: tpage 8 → offset 256px (right half)
Section N-1: TIM texture
Standard PlayStation TIM format texture containing all stage textures in an atlas.
Coordinate system
All geometry sections use the same 3D coordinate format (X, Z, Y):
- X: Left/right
- Z: Height (stored as negative in PSX format, positive = down; ground plane typically has Z=0)
- Y: Forward/back
Typical ranges:
- Ground plane: X, Y from -6000 to +6000, Z=0
- Sky dome: Large coordinates (~30000) forming a hemisphere
Important: PSX uses positive Z as downward. When rendering in standard 3D systems (positive Y = up), negate the Z coordinate: render_y = -psx_z
Example file analysis
STAGE00 (JP version) (147,820 bytes)
| Section |
Offset |
Size |
Content |
Texture Page |
Palette |
| 0 |
0x001C |
8 |
Metadata |
- |
- |
| 1 |
0x0024 |
10,836 |
Ground plane (832 verts, 0 tris, 208 quads) |
6 (0-255px) |
0 |
| 2 |
0x2A78 |
3,020 |
Sky dome (225 verts, 76 tris, 0 quads) |
8 (256-511px) |
1 |
| 3 |
0x3644 |
852 |
Sky ring mid (64 verts, 0 tris, 16 quads) |
8 (256-511px) |
1 |
| 4 |
0x3998 |
436 |
Sky ring low (32 verts, 0 tris, 8 quads) |
8 (256-511px) |
2 |
| 5 |
0x3B4C |
132,640 |
TIM texture (512×256, 8bpp, 3 palettes) |
- |
- |
STAGE60 (JP version) (226,348 bytes)
| Section |
Offset |
Size |
Content |
Texture Page |
Palette |
| 0 |
0x0048 |
8 |
Metadata |
- |
- |
| 1 |
0x0050 |
7,788 |
Ground plane (596 verts, 20 tris, 134 quads) |
10 (512-639px) |
4 |
| 2 |
0x1EBC |
3,020 |
Sky dome (225 verts, 76 tris, 0 quads) |
6 (0-127px) |
0 |
| 3 |
0x2A88 |
436 |
Sky ring (32 verts, 0 tris, 8 quads) |
6 (0-127px) |
0 |
| 4 |
0x2C3C |
852 |
Sky ring (64 verts, 0 tris, 16 quads) |
6 (0-127px) |
0 |
| 5-15 |
various |
various |
Environmental objects |
6, 8, 10 |
0-6 |
| 16 |
0x640C |
200,736 |
TIM texture (768×256, 8bpp, 7+ palettes) |
- |
- |
Note: STAGE60 demonstrates mixed geometry (triangles + quads in same section) and complex texture atlas usage with multiple texture pages and palettes.
Related formats
- Battle Model Format (PSX) - Similar polygon structures
- TIM Format - Texture format specification
- LZS Format - Compression used for battle stage files