This document explains how exterior decor objects work in the engine, how they are loaded, how visibility is toggled, and what modders must edit when changing island content.
This is about exterior 3D island decors (T_DECORS), not scene actors.
.ILE/.OBL) per cube.Exterior decor objects are stored in island/cube data and rendered by the exterior engine path.
Relevant code:
SOURCES/3DEXT/LOADISLE.CPP (LoadIsland, LoadCube)SOURCES/3DEXT/DECORS.CPP (FixeObjetsDecorsInvisibles, AffichageObjetDecorsZBuf)SOURCES/3DEXT/LBA_EXT.H (T_DECORS, flags)When a cube is loaded, its own DOB chunk is loaded into ListDecors:
LoadCube() sets NbObjDecors and ListDecors from cube data.Implication:
Beta >> 16)Each decor can encode a game-variable condition in the high 16 bits of Beta:
numvar = ptrobj->Beta >> 16Engine rule (from FixeObjetsDecorsInvisibles):
numvar > 0: hide if ListVarGame[numvar] != 0numvar < 0: hide if ListVarGame[-numvar] == 0numvar == 0: no variable-based visibility conditionExample (specific to Citadel citabau cube 42): both +83 and -83 exist for opposite variants, where var 83 toggles spaceship vs occupied/barbed-wire set.
For Citadel island cube 42, traces showed:
body=101 with numvar=+83 (spaceship set)numvar=-83Behavior:
ListVarGame[83] == 0 -> +83 visible, -83 hidden (spaceship visible)ListVarGame[83] != 0 -> +83 hidden, -83 visible (occupied props visible)So this swap is done by decor visibility logic, that allows us to enrich the same island model without need to load another island model.
These are separate systems:
citadel vs citabau) in InitGrilleExt:SOURCES/EXTFUNC.CPPThe only 2 places in the game where the whole island model needs to be switched is citadel (initial island model when we have storm) vs citabau, and the volcano island before the ceremony vs after the ceremony. A corresponding API exist in IdaJS to force those models (see the storm sample). In all other cases, we can reuse the same island model and just swap specific decor subsets through game variables.
SOURCES/3DEXT/DECORS.CPP (Beta >> 16 + ListVarGame)So you can be on the same island model and still swap specific decor subsets through game variables.
From SOURCES/3DEXT/LOADISLE.CPP and SOURCES/3DEXT/LBA_EXT.H:
IsleMapIndex) in .ILE.6:
INF: cube infosDOB: decor objects (T_DECORS list)GRD: ground polysTXD: texture defsY: heightmapLUM: lightmapTo add/change decor objects, you edit cube DOB data.
To add a new logical cube layout location, you edit:
MAX_CUBES_PER_ISLE = 20 (SOURCES/3DEXT/LBA_EXT.H)MAX_OBJ_DECORS = 200 (SOURCES/3DEXT/LBA_EXT.H)In SOURCES/DEFINES.H:
MIN/MAX_ISLE_OBJ_MEMMIN/MAX_CUBE_INFOS_MEMMIN/MAX_LIST_DECORS_MEMMIN/MAX_MAP_PGROUND_MEMMIN/MAX_LIST_TEXDEF_MEMMIN/MAX_MAP_SOMMETY_MEMMIN/MAX_MAP_INTENSITY_MEMIf you increase cube/decor complexity, you may need both:
GRM (scene fragments) is a separate brick-overlay system and is not the same mechanism as T_DECORS visibility flags.
For detailed GRM internals, life opcodes/functions, persistence, and IdaJS usage, see:
A practical way to force decor variant swaps is to set the controlling game variable after scene load.
Example (Citadel occupation switch variable 83):
scene.addEventListener(scene.Events.afterLoadScene, (sceneId, loadMode) => {
if (loadMode === scene.LoadModes.NewGameStarted) return;
const gameStore = useGameStore();
const VAR_CITADEL_OCCUPATION_DECORS = 83;
const OCCUPIED_SET = 8; // nonzero => hide +83, show -83
const NORMAL_SET = 0; // zero => show +83, hide -83
// Optional guard for target scenes only.
// if (!isCitadelExterior(sceneId)) return;
scene.setGameVariable(
VAR_CITADEL_OCCUPATION_DECORS,
gameStore.showOccupiedSet ? OCCUPIED_SET : NORMAL_SET
);
// ... more code
});
Notes:
numvar, body, visible/hidden state), using the debug tracing mechanism (LBA_IDA_TRACE_DECORS env variable, see below).DOB decor list,Beta >> 16 conditions consistent,.OBL bank.Decor not appearing:
Body & 0xFFFFnumvar (+N vs -N)Works in one area but not another:
Crash/memory issues after data growth:
MAX_* limits or memory poolsSOURCES/3DEXT/DECORS.CPP includes optional trace controlled by:
LBA_IDA_TRACE_DECORS=1 (enable)LBA_IDA_TRACE_DECORS=0 (disable)It logs, per visibility pass:
numvar, var value, applied rule, final stateThis is useful to compare save states and confirm which variable drives each decor set.