![]() |
Jo Engine 2026.11.1
Jo Sega Saturn Engine
|
This file is made by Compiler/COMMON/jo_level/gen.py from Compiler/COMMON/jo_level/schema.py: change the schema, not this file.
A level (.JOL) holds the data of a level of a game: terrain, models and their collisions, plants, decals... The engine loads it with jo_level_load() (module JO_COMPILE_WITH_LEVEL_MODULE = 1); the tools of Compiler/COMMON/jo_level write it (Python), check it and print it.
| Type | Size | Description |
|---|---|---|
| u8, i8, u16, i16, u32, i32 | 1, 2, 4 | Integers (big endian) |
| fixed | 4 | Q16.16 fixed point number (jo_fixed) |
| string | 4 | Offset of a string in the STRS section, 0xFFFFFFFF: no string |
| offset | 4 | Offset of an array from the start of the section (0: empty) |
| index | 2 | Index of a record of another section, 0xFFFF: none |
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | magic | u32 | 0 | v1 | 'JOLV' |
| 4 | version | u16 | 0 | v1 | Version of the file format (FORMAT_VERSION: header and directory) |
| 6 | flags | u16 | 0 | v1 | Reserved (0) |
| 8 | size | u32 | 0 | v1 | Size of the file (bytes) |
| 12 | section_count | u32 | 0 | v1 | Number of entries of the directory (after this header) |
Size: 16 bytes.
One entry per section, after the file header.
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | id | u32 | 0 | v1 | Identifier: 4 characters |
| 4 | version | u16 | 0 | v1 | Version of the section |
| 6 | flags | u16 | 0 | v1 | FLAG_xxx (REQUIRED: 1) |
| 8 | offset | u32 | 0 | v1 | Offset of the section from the start of the file (multiple of 4) |
| 12 | size | u32 | 0 | v1 | Size of the section (bytes, header, records and arrays) |
| 16 | header_size | u16 | 0 | v1 | Size of the header of the section (bytes) |
| 18 | record_size | u16 | 0 | v1 | Size of a record (bytes) |
| 20 | record_count | u32 | 0 | v1 | Number of records (after the header) |
Size: 24 bytes.
The level: its name. The first section of the file.
Header:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | name | string | 0xFFFFFFFF | v1 | Name of the level |
| 4 | flags | u32 | 0 | v1 | Reserved (0) |
Size: 8 bytes.
Strings of the level (names of files, of classes...): ASCII, each one ended by a zero. A field of type string is the offset of the string in this section.
Height map of the ground: n x n vertices, one every cell units. The vertex (i, j) is at x = origin_x + (i - (n - 1) / 2) x cell, z = origin_z + (j - (n - 1) / 2) x cell. The meshes of the ground are built from it when the level is loaded.
Header:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | n | u16 | 0 | v1 | Vertices per side |
| 2 | cell | u16 | 0 | v1 | Distance between 2 vertices (world units) |
| 4 | origin_x | i32 | 0 | v1 | Center of the height map (world units) |
| 8 | origin_z | i32 | 0 | v1 | Center of the height map (world units) |
| 12 | heights | offset | 0 | v1 | Heights: n x n i16, row after row (j), Y up (world units) |
| 16 | colors | offset | 0 | v1 | Colors of the vertices: n x n RGB 555 of the Saturn (the light of the sun is baked) |
| 20 | hole_count | u32 | 0 | v1 | Number of entries of holes |
| 24 | holes | offset | 0 | v1 | Vertices under a building: u16 j x n + i. A quad with its 4 vertices in this list is not drawn. With the flag 0x8000 the quad is drawn, sorted behind the building |
Size: 28 bytes.
Arrays (after the header and the records, aligned on 4 bytes):
Plants and rocks placed on the ground.
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | x | i32 | 0 | v1 | Position (world units) |
| 4 | y | i32 | 0 | v1 | Height of the foot, Y up (world units) |
| 8 | z | i32 | 0 | v1 | Position (world units) |
| 12 | kind | u16 | 0 | v1 | Kind: 0 palm tree, 1 bush, 2 fern, 3 rock, 4 tree of the inland |
| 14 | angle | i16 | 0 | v1 | Rotation around Y (degrees) |
| 16 | scale | u16 | 0 | v1 | Scale x 256 (256: 1) |
Size: 20 bytes.
3D models of the level (JOM files) and their collisions.
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | file | string | 0xFFFFFFFF | v1 | JOM file (in the root directory of the CD) |
| 4 | first_box | u16 | 0 | v1 | First collision box (MBOX) |
| 6 | box_count | u16 | 0 | v1 | Number of collision boxes |
| 8 | has_floor | u16 | 0 | v1 | 1: the model has a floor the player can walk on |
| 10 | floor | i16[5] | 0 | v1 | Floor: x0, z0, x1, z1 (model space), height (Y up) (world units) |
Size: 20 bytes.
Collision boxes of the models (vertical boxes, model space).
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | x | i16 | 0 | v1 | Center (world units) |
| 2 | z | i16 | 0 | v1 | Center (world units) |
| 4 | half_x | i16 | 0 | v1 | Half size (world units) |
| 6 | half_z | i16 | 0 | v1 | Half size (world units) |
Size: 8 bytes.
Models placed in the level (buildings).
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | model | index | 0xFFFF | v1 | Model seen from far away (MODL) |
| 2 | near_model | index | 0xFFFF | v1 | Model seen from near (MODL), NO_INDEX: the same |
| 4 | inside_model | index | 0xFFFF | v1 | Model of the inside, drawn from near (MODL), NO_INDEX: none |
| 6 | yaw | i16 | 0 | v1 | Rotation around Y (degrees) |
| 8 | x | i32 | 0 | v1 | Position (world units) |
| 12 | y | i32 | 0 | v1 | Position, Saturn Y (down) (world units) |
| 16 | z | i32 | 0 | v1 | Position (world units) |
| 20 | half_x | i16 | 0 | v1 | Collision box when the model has none (model space), 0: no collision (world units) |
| 22 | half_z | i16 | 0 | v1 | Collision box when the model has none (world units) |
Size: 24 bytes.
Flat decals lying at the sea level on the bottom of the lagoon.
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | kind | u16 | 0 | v1 | Kind: 0 coral, 1 brain coral, 2 starfish, 3 rocks, 4 sea grass, 5 anchor, 6 barrel |
| 2 | angle | i16 | 0 | v1 | Rotation around Y (degrees) |
| 4 | size | u16 | 0 | v1 | Size (world units) |
| 8 | x | i32 | 0 | v1 | Position (world units) |
| 12 | z | i32 | 0 | v1 | Position (world units) |
Size: 16 bytes.
The things of the level that live: an entity has a class (its behavior, given by the engine or by the game), a position, a health, properties (PROP) and links to other entities (LINK). See jo_engine/jo/entity.h and the classes of the engine in Documentation/level_format.md.
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | name | string | 0xFFFFFFFF | v1 | Name (unique in the level: the links and the game find the entity by it) |
| 4 | cls | string | 0xFFFFFFFF | v1 | Class: a class of the engine (explosive, relay, trigger...) or of the game |
| 8 | model | string | 0xFFFFFFFF | v1 | JOM file drawn at its position, NO_STRING: none |
| 12 | x | i32 | 0 | v1 | Position (world units) |
| 16 | y | i32 | 0 | v1 | Position, Saturn Y (down): the foot of the entity (world units) |
| 20 | z | i32 | 0 | v1 | Position (world units) |
| 24 | yaw | i16 | 0 | v1 | Rotation around Y (degrees) |
| 26 | health | i16 | 0 | v1 | Health at the start, 0: it can't be damaged |
| 28 | flags | u16 | 0 | v1 | ENTITY_FLAG_xxx: DISABLED 1, SOLID 2, HIDDEN 4 |
| 30 | radius | u16 | 0 | v1 | Cylinder (collisions, bullets, zone of a trigger): radius (world units) |
| 32 | height | u16 | 0 | v1 | Cylinder: height above the foot (world units) |
| 34 | first_prop | u16 | 0 | v1 | First property (PROP) |
| 36 | prop_count | u16 | 0 | v1 | Number of properties |
| 38 | first_link | u16 | 0 | v1 | First link of which it is the source (LINK) |
| 40 | link_count | u16 | 0 | v1 | Number of links |
Size: 44 bytes.
Properties of the entities: a name and a value (PROPERTY_xxx type). The classes read them by name, with a default value when the property is missing.
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | key | string | 0xFFFFFFFF | v1 | Name |
| 4 | type | u16 | 0 | v1 | PROPERTY_xxx: INT 0, FIXED 1, STRING 2 |
| 8 | value | i32 | 0 | v1 | Value (the offset of the string for a STRING) |
Size: 12 bytes.
Links between the entities: when the event of the source happens, the action is sent to the target after the delay. The links of an entity follow each other (ENTI.first_link).
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | event | u16 | 0 | v1 | EVENT_xxx of the source |
| 2 | action | u16 | 0 | v1 | ACTION_xxx sent to the target |
| 4 | target | index | 0xFFFF | v1 | Target (ENTI), NO_INDEX: the source itself |
| 6 | delay | u16 | 0 | v1 | Delay (ms) |
| 8 | param | i32 | -1 | v1 | Parameter of the action (the damage of DAMAGE), -1: the parameter of the event |
Size: 12 bytes.
The rooms of an inside level (a base, a building): each cell has its model (walls, floor, ceiling, in world coordinates) and a box where the player can walk, with its floor (flat or a slope: the stairs). The cells overlap at the doors: the player walks from one to the other. The engine draws the cell of the camera and the cells seen through its portals (PRTL), see jo_engine/jo/cell.h.
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | model | string | 0xFFFFFFFF | v1 | JOM file of the cell (world coordinates) |
| 4 | x0 | i32 | 0 | v1 | Box where the player can walk: smallest X (world units) |
| 8 | z0 | i32 | 0 | v1 | Smallest Z (world units) |
| 12 | x1 | i32 | 0 | v1 | Biggest X (world units) |
| 16 | z1 | i32 | 0 | v1 | Biggest Z (world units) |
| 20 | floor0 | i32 | 0 | v1 | Floor, Saturn Y (down): at x0 (slope X), at z0 (slope Z) or everywhere (flat) (world units) |
| 24 | floor1 | i32 | 0 | v1 | Floor at x1 (slope X) or at z1 (slope Z) (world units) |
| 28 | slope | u16 | 0 | v1 | CELL_SLOPE_xxx: FLAT 0, X 1, Z 2 |
| 30 | first_portal | u16 | 0 | v1 | First portal of the cell (PRTL) |
| 32 | portal_count | u16 | 0 | v1 | Number of portals |
| 34 | yaw | i16 | 0 | v2 | Rotation of the box around its center (Y axis, the same as the yaw of INST): a building turned on the terrain (degrees) |
| 36 | reflection | string | 0xFFFFFFFF | v3 | JOM file drawn in the mirrors of the cell instead of its model (a simpler model: bigger quads, no floor), NO_STRING: its model and the cells next to it |
Size: 40 bytes.
Openings of the cells (doors, windows, stairs): a quad through which another cell is seen. The portals of a cell follow each other (CELL.first_portal).
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | cell | index | 0xFFFF | v1 | Cell seen through the portal (CELL), NO_INDEX: the outside (the terrain around a building, see jo_cells_outside_view()) |
| 2 | kind | u16 | 0 | v1 | PORTAL_xxx: DOOR 0 (an opening), MIRROR 1 (a mirror of the cell), WINDOW 2 (seen through from the cell only) |
| 4 | points | i32[12] | 0 | v1 | The 4 corners: x, y (Saturn Y), z of each corner (world units) |
Size: 52 bytes.
Definitions of the weapons of the player (and later of the enemies): a file of definitions shared by the levels (for instance DEFS.JOL). The engine runs them (ammo, fire rate, reload, change of weapon, see jo_engine/jo/weapon.h), the game draws them. The times are frames of the game.
Record:
| Offset | Field | Type | Default | Since | Description |
|---|---|---|---|---|---|
| 0 | name | string | 0xFFFFFFFF | v1 | Name |
| 4 | model | string | 0xFFFFFFFF | v1 | JOM file of the weapon and the hands (first person view) |
| 8 | x | fixed | 0 | v1 | Position of the model in front of the camera: right (world units, Q16.16) |
| 12 | y | fixed | 0 | v1 | Down (world units, Q16.16) |
| 16 | z | fixed | 0 | v1 | Forward (world units, Q16.16) |
| 20 | muzzle_y | fixed | 0 | v1 | The muzzle in the model: height (the axis of the barrel) (world units, Q16.16) |
| 24 | muzzle_z | fixed | 0 | v1 | The muzzle in the model: forward (world units, Q16.16) |
| 28 | convergence | u16 | 0x258 | v1 | The axis of the barrel crosses the line of sight at this distance (the crosshair) (world units) |
| 30 | automatic | u16 | 0 | v1 | 1: it fires while the button is held, 0: one shot per press |
| 32 | fire_delay | u16 | 4 | v1 | Frames between 2 shots |
| 34 | damage | u16 | 0x19 | v1 | Damage of a bullet |
| 36 | spread | u16 | 0 | v1 | Spread of the bullets: the offset at 1000 units (world units) |
| 38 | recoil | u16 | 0xA | v1 | The model goes back (frames of recoil) |
| 40 | kick | u16 | 2 | v1 | The view goes up at each shot (degrees) |
| 42 | magazine | u16 | 0x1E | v1 | Bullets of a magazine |
| 44 | ammo_type | u16 | 0 | v1 | Type of ammunition (the weapons with the same type share their reserve) |
| 46 | ammo_start | u16 | 0 | v1 | Reserve at the start (not in the magazine) |
| 48 | ammo_max | u16 | 0x3E7 | v1 | Reserve at most |
| 50 | reload_time | u16 | 0x32 | v1 | Frames of the reload |
| 52 | switch_time | u16 | 0xC | v1 | Frames to take this weapon (it comes up), the other one goes down in the same time |
| 56 | flash | string | 0xFFFFFFFF | v1 | Base name of the muzzle flashes (FLASH: FLASH1.BIN, FLASH2.BIN, FLASH3.BIN) |
| 60 | flash_size | u16 | 0x16 | v1 | Size of the muzzle flash (pixels) |
| 62 | casing | u16 | 0 | v1 | 1: the casings are ejected |
| 64 | sound_shot | string | 0xFFFFFFFF | v1 | PCM file of the shot |
| 68 | sound_reload | string | 0xFFFFFFFF | v1 | PCM file of the reload |
| 72 | shot_volume | u16 | 0x78 | v1 | Volume of the shot (0 to 127) |
Size: 76 bytes.
Events of an entity (LINK.event): what happened to the source.
| Value | Name | Description |
|---|---|---|
| 0 | EVENT_TRIGGER | The entity received the action TRIGGER or USE (a relay, a switch that is used) |
| 1 | EVENT_ENTER | The player entered the entity (a trigger zone) |
| 2 | EVENT_EXIT | The player left the entity (a trigger zone) |
| 3 | EVENT_DAMAGE | The entity received damage and is still alive (param: the damage) |
| 4 | EVENT_DEATH | The health of the entity reached 0 (an explosive explodes) |
Actions sent to an entity (LINK.action): what the target does.
| Value | Name | Description |
|---|---|---|
| 0 | ACTION_TRIGGER | Fire the event TRIGGER of the target (a relay passes it on) |
| 1 | ACTION_DAMAGE | Damage the target (param: the damage) |
| 2 | ACTION_KILL | The health of the target goes to 0 (event DEATH) |
| 3 | ACTION_ENABLE | The target is active again (drawn, solid, it reacts) |
| 4 | ACTION_DISABLE | The target is not active (not drawn, not solid, it ignores the actions but ENABLE) |
| 5 | ACTION_TOGGLE | ENABLE or DISABLE |
| 6 | ACTION_USE | The target is used (like the player does: a switch, a door). The default: TRIGGER |
| 7 | ACTION_OPEN | Open the target (a door, a mover) |
| 8 | ACTION_CLOSE | Close the target (a door, a mover) |
Floors of the cells (CELL.slope).
| Value | Name | Description |
|---|---|---|
| 0 | CELL_SLOPE_FLAT | The floor is flat (floor0) |
| 1 | CELL_SLOPE_X | The floor goes from floor0 at x0 to floor1 at x1 (stairs along X) |
| 2 | CELL_SLOPE_Z | The floor goes from floor0 at z0 to floor1 at z1 (stairs along Z) |
Kinds of portals (PRTL.kind).
| Value | Name | Description |
|---|---|---|
| 0 | PORTAL_DOOR | An opening: the other cell is seen through it |
| 1 | PORTAL_MIRROR | A mirror: the cell (and the cells next to it) is drawn again, reflected by the plane of the portal, clipped to its rectangle on the screen (see jo_cells_draw_mirror()). The wall has a hole there |
| 2 | PORTAL_WINDOW | An opening seen through from the cell only (an embrasure, a window too high): from the other side, the cell is not seen through it (the hole is covered, for instance by a dark polygon) |
Types of the properties of the entities (PROP.type).
| Value | Name | Description |
|---|---|---|
| 0 | PROPERTY_INT | value is an integer (i32) |
| 1 | PROPERTY_FIXED | value is a Q16.16 fixed point number |
| 2 | PROPERTY_STRING | value is the offset of a string in STRS |
Flags of the entities (ENTI.flags).
| Value | Name | Description |
|---|---|---|
| 0x0001 | ENTITY_FLAG_DISABLED | Not active at the start (see the action ENABLE) |
| 0x0002 | ENTITY_FLAG_SOLID | The player and the bullets collide with its cylinder (radius, height) |
| 0x0004 | ENTITY_FLAG_HIDDEN | Its model is not drawn (a trigger zone, a relay) |
The value of ENTI.cls. A game registers its own classes with jo_entity_register_class() (a class with the name of a class of the engine replaces it). A property missing in the level gets its default value.
Properties of all the entities (the classes of the game too).
| Property | Type | Default | Description |
|---|---|---|---|
| far_model | STRING | "" | JOM file drawn farther than far_distance (fewer polygons), "": the model |
| far_distance | INT | 600 | The far model is drawn farther (world units, on the ground) |
| draw_distance | INT | 2000 | The model is not drawn farther (world units, on the ground): each polygon costs CPU time, even small far away |
| box_x | INT | 0 | With box_z: the collisions of the player and the vehicles use a box turned with the entity (half sizes, world units) instead of its cylinder (a partition, a counter). The bullets keep the cylinder |
| box_z | INT | 0 | See box_x |
The default class: its model at its position. An entity with an unknown class is an entity.
Passes the action TRIGGER (or USE) on: its links TRIGGER send their actions, with their delays (a sequence of events: a fuse, a chain of doors). Flag HIDDEN.
A zone: its cylinder (radius, height above the foot). Fires ENTER when the player goes in, EXIT when he goes out. Flag HIDDEN.
| Property | Type | Default | Description |
|---|---|---|---|
| once | INT | 0 | 1: the zone is disabled after the first ENTER |
A place where the player appears (the start of a level, the end of a zone_link): its position and its yaw. Flag HIDDEN.
A passage to another level (stairs, a door, an elevator): when the player goes in its cylinder (or when it receives TRIGGER or USE), the game is asked to load the level and to put the player on the spawn (jo_entities_get_zone_request()). The player must go out of it before (he may start in it). Flag HIDDEN.
| Property | Type | Default | Description |
|---|---|---|---|
| level | STRING | "" | JOL file of the level |
| spawn | STRING | "" | Name of the entity (class spawn) of the level where the player appears |
Explodes when its health reaches 0 (bullets, an explosion near it, the action KILL): fireballs and smoke (sprites NAME1.BIN, NAME2.BIN... opaque with end codes), flash of the screen, shake of the camera (module JO_COMPILE_WITH_EFFECTS_MODULE = 1), sound, then its dead model. After the delay, it damages what is around it (the other explosives: chain reactions) and the player (the game gets the damage, see jo_entities_set_area_damage_callback()). Flag SOLID: the bullets hit it.
| Property | Type | Default | Description |
|---|---|---|---|
| damage | INT | 100 | Damage at the center (less farther, 0 at the radius) |
| radius | INT | 200 | Radius of the damage (world units) |
| delay | INT | 150 | Delay of the damage after the explosion (ms) |
| size | INT | 100 | Size of the fireball (world units) |
| fire | STRING | "FIRE" | Base name of the frames of the fire (FIRE1.BIN to FIRE4.BIN) |
| fire_frames | INT | 4 | Number of frames of the fire (1 to 4) |
| smoke | STRING | "SMOKE" | Base name of the frames of the smoke |
| smoke_frames | INT | 2 | Number of frames of the smoke (0: no smoke, up to 4) |
| sound | STRING | "" | PCM file (8 bits mono), "": none |
| dead_model | STRING | "" | JOM file drawn after the explosion, "": nothing (and not solid any more) |
| flash | INT | 120 | Brightness of the flash of the screen (0 to 255), less far from the player |
| shake | INT | 6 | Shake of the camera (world units), less far from the player |
An animated model (a skeleton, see jo_3d_model_draw_pose()): plays an animation (a soldier who waits, a flag). The classes of the game (enemies) are actors too (see jo_actor_spawn()). The far model has the same skeleton.
| Property | Type | Default | Description |
|---|---|---|---|
| animation | STRING | "idle" | Name of the animation of the model played at the start |
A vehicle driven by the player (see jo/vehicle.h): its model has a skeleton with the bones wheel_fl, wheel_fr, wheel_rl, wheel_rr (they turn), steer_fl, steer_fr (they steer) and steering (the steering wheel). The 4 wheels follow the ground (the game gives it, see jo_vehicles_set_ground_callback()), the solid entities stop it (a hit at speed damages them). Flag SOLID: the player walks around it.
| Property | Type | Default | Description |
|---|---|---|---|
| cockpit_model | STRING | "" | JOM file drawn when the player drives (what the driver sees, the same skeleton), "": the model |
| wheelbase | INT | 106 | Distance between the front and the rear axles (world units) |
| track | INT | 78 | Distance between the left and the right wheels (world units) |
| wheel_radius | INT | 19 | Radius of the wheels (world units) |
| max_steer | INT | 30 | Angle of the front wheels at most (degrees) |
| max_speed | INT | 900 | Top speed (world units per second; backward: a third) |
| acceleration | INT | 280 | Acceleration of the engine (world units per second per second) |
| braking | INT | 800 | Deceleration of the brakes (world units per second per second) |
| impact | INT | 150 | Damage of a hit at the top speed |
For the next versions of the engine:
An engine reads the levels written for an older engine (the missing fields get their default values) and, when no section is required, the levels written for a newer engine (it ignores what it doesn't know).
Writing a level in Python: