This document explains how GRM works in the original engine and how to control/read it from life scripts and IdaJS.
GRM is a zone-driven brick overlay system for grid-based map cubes (the isometric BufCube world).
At runtime, a GRM zone can:
IncrustGrm), orDesIncrustGrm).GRM is implemented in:
SOURCES/GRILLE.CPP (IncrustGrm, DesIncrustGrm, RedrawGRMs)SOURCES/GERELIFE.CPP (LM_SET_GRM)GRM uses zone type 3.
object.ZoneTypes.Fragment.T_ZONE in SOURCES/COMMON.H.Important T_ZONE fields for GRM:
Type must be 3 (Fragment/GRM zone)Num is the logical zone value used by life scripts (LM_SET_GRM zoneId, onOff)Info0 is the GRM resource index (numgrm) to loadInfo2 is runtime ON/OFF state used by engine redraw/save/restoreX0..Z1 bounds define affected map regionInitialization when scene is loaded (DISKFUNC.CPP):
Type==3) are reset with Info1 = 0Info2 is then controlled by scripts/savegame stateIncrustGrm(ptrz)numgrm = ptrz->Info0BKG.HQR index:
BkgHeader.Grm_Start + GriHeader->My_Grm + numgrmX0,Y0,Z0) and GRM chunk dimensions (dx,dy,dz) to copy brick column slices into BufCubeIn practice: this patches cube bricks with predefined fragment data.
DesIncrustGrm(ptrz)X0..X1, Y0..Y1, Z0..Z1)GetAdrColonneMap + DecompColonne)BufCubeIn practice: this removes overlay and restores original map data in zone area.
RedrawGRMs() re-applies every GRM zone where Info2 != 0.
This is called during map init (InitGrille) and also on savegame load path for GRM zones.
LM_SET_GRM (opcode 76 / 0x4C)Definition:
(zoneValue, onOff)Type==3 and Num==zoneValue:
onOff != 0 and currently OFF (Info2==0) -> IncrustGrmonOff == 0 and currently ON (Info2!=0) -> DesIncrustGrmInfo2 = onOffNote in source comment: "ne marche que si pas autre grm" (designed assuming non-overlapping GRM interactions).
Related code:
SOURCES/GERELIFE.CPP (case LM_SET_GRM)There is no dedicated LF_* function for GRM.
So from classic life script, GRM state cannot be directly queried via a dedicated opcode.
Common pattern:
LM_SET_VAR_GAME) and read it with LF_VAR_GAME.Zone runtime fields are persisted in savegames, including GRM state:
Info1, Info2, Info3, Info7 for all zonesType==3), if restored Info2 != 0, engine calls IncrustGrmSo GRM ON/OFF survives save/load through zone state serialization.
Use life opcode wrapper:
// objectId is the actor context executing the life command (often 0 for Twinsen in mod scripts)
ida.life(ida.Life.LM_SET_GRM, objectId, zoneValue, 1); // ON
ida.life(ida.Life.LM_SET_GRM, objectId, zoneValue, 0); // OFF
There is no direct LF_GRM, but you can inspect zones:
for (const zone of scene.zones) {
if (zone.getType() === object.ZoneTypes.Fragment) {
const zoneValue = zone.getZoneValue(); // T_ZONE.Num
const regs = zone.getRegisters(); // [Info0..Info7]
const grmResource = regs[0]; // Info0
const grmState = regs[2]; // Info2 (runtime on/off state)
}
}
Important:
Info2 via setRegisters alone does not execute overlay copy/remove logic.LM_SET_GRM.When authoring/modifying scene data:
Type=3 (Fragment).Num (zone value) used by scripts.Info0 to target GRM resource index.X0..X1, Y0..Y1, Z0..Z1) to match affected area.BKG.HQR for current style (My_Grm offset applies).Practical caution:
GRM is separate from exterior decor (T_DECORS) visibility masking:
BufCube) via zone overlays.T_DECORS.Beta >> 16 and ListVarGame checks.Use GRM for map brick fragment toggles, and decor rules for object-model toggles.
3 (Fragment)LM_SET_GRM(zoneValue, onOff)LF_GRM does not exist)T_ZONE.Info2T_ZONE.Info0Info2 persisted and reapplied)