Global variable bounded-context map

June 4, 2026 · View on GitHub

Status: DRAFT / living document (v0). A reasoning overlay, not a refactor plan. Domain assignments are provisional and meant to be argued with. Nothing here changes behaviour — the polyrec/projrec equivalence tests freeze the engine, so this map exists to make the implicit global bus legible, not to move code.

Why this exists

The engine communicates almost entirely through ~200 global variables declared in SOURCES/C_EXTERN.H and defined in SOURCES/GLOBAL.CPP (plus a handful defined in KEYBOARD.CPP, MESSAGE.CPP, OBJECT.CPP). There is no encapsulation boundary, so a function cannot be reasoned about in isolation: to know what HitObj does you must know GodMode gates it, and to change GodMode safely you must know everyone who reads it. That is a web, not a tree, and webs do not fit in one's head.

This map assigns each global to the domain that owns it (writes it, is conceptually responsible for it) and flags the domains that merely read it. The reads across a boundary are the coupling edges. The goal is the first step of a strangler-fig: draw the boundaries explicitly over the frozen code, the way the platform seam (PR #284–#286) wraps SDL without rewriting the renderer. You cannot delete the bus, but you can publish its schedule.

The eight domains

  1. Gameplay / simulation — entities, AI behaviour, inventory and stats, zones, player physics and control state.
  2. Rendering — projection, sort tree, cameras, palettes, shadow, sprite scaling, cinema framing, environment effects.
  3. World / cube transform — cube and world coordinate systems, scene/cube origins, transition coordinates. The busiest coupling axis; likely the first strangler-fig target.
  4. Resources / assets — HQR handles, memory-pool budgets, body/anim/sprite/texture buffers.
  5. Audio — sample and music volumes, the ambiance scheduler, stereo.
  6. Platform / IO — raw input device state, display mode, timing, file paths, version banners, CD/video.
  7. UI / text — menus, dialog and speech presentation, on-screen text buffers, fonts.
  8. Orchestration / lifecycle — main-loop and transition flags, scene mode, save/restore state, checksum.

A ninth concern cuts across all of them and is not a domain: persistence. The block under C_EXTERN.H:302 ("Globales sauvées dans les fichiers de sauvegarde .LBA") marks ~50 globals as savegame state spanning gameplay, world, audio params, and lifecycle. Persistence is an aspect, not a bounded context — worth tracking separately when the save format is touched.

The engine / game / platform lens

A deeper axis cuts through these domains: which code is reusable engine, which is LBA2-specific game, which is platform. It is validated empirically against the sibling LBA1 source (lba1-classic) at the module level — see ENGINE_GAME_SEAM.md for the three-way diff and evidence. Short version: Rendering, World/cube transform, and Resources are engine; Platform/IO is platform; Gameplay is game on an engine substrate; Audio, UI/text, and Orchestration are engine mechanism with game content. The membrane between engine and game is the Life/Track script VM (GERELIFE/GERETRAK), which is present in all three Adeline trees. Prefer strangler-fig facades on engine seams — they pay off for LBA1 too.

Domains and their globals

Cross-boundary readers are the domains (beyond the owner) that consume the value. Array/cluster groups are collapsed to one row. means single-owner / no significant cross-read found at v0.

Gameplay / simulation (~55)

GlobalRoleCross-boundary readers
ListObjet[]The entity table (all actors)Rendering, World, UI, Audio
NbObjetsEntity countRendering
ListExtra[], ListDart[]Projectiles / extras / dartsRendering
ListComportement[], PtrComportementAI behaviour table + currentOrchestration (save)
Comportement, SaveComportement, SaveComportementHeroHero behaviour + saved copiesOrchestration
ListVarCube[], ListVarGame[]Scene/game script variablesRendering, UI, Orchestration
MagicLevel, MagicPointMagic statsRendering (gauge), UI
MagicBall, MagicBallType, MagicBallCount, MagicBallFlagsMagic-ball stateRendering
MagicHitForce[], MagicBallHitForce[], MagicBallSprite[]Combat damage tablesRendering
Weapon, ActionNormal, InventoryActionAction / weapon modeUI
NbGoldPieces, NbZlitosPieces, NbLittleKeys, NbCloverBoxInventory countsUI
TabInv[]Inventory object tableUI
NbZones, ListZoneTrigger zonesWorld
StartYFalling, RealFalling, StepFalling, RealShifting, StepShiftingFalling / shifting physicsWorld
PtrZoneClimb, FlagClimbingLadder-climb state
LastJoyFlag, LastMyJoy, LastMyFire, Jumping, PushingPlayer control state (gameplay-written, saved)Platform (source), Orchestration (save)
NumObjFollow, ExtraConque, PingouinActifFollow / conque / penguin mechanics
ListArdoise[], CurrentArdoise, NbArdoiseHolomap clue slateUI
ListBrickTrack, NbBrickTrackTrack-script working data
Bulle, SaveBodyHero, AnimNumObj, APtObjHero body / speech-bubble / anim cursorRendering, UI
FlagReajustPosTwinsenReposition-on-cube-change flagWorld

Correction applied (vs the seed taxonomy): LastJoyFlag/Jumping/Pushing/LastMyJoy/LastMyFire look like Platform input but are written by gameplay logic (OBJECT.CPP, BUGGY.CPP) off the raw input, and are part of the savegame, so they belong here. Verified by grepping the assignment sites.

Rendering (~45)

GlobalRoleCross-boundary readers
ListTri[]Sort tree (painter's order)
PtrPal, PtrPalNormal, PtrPalCurrent, PtrPalEclair, PtrPalBlack, PtrTransPalActive / variant palettesResources (writes on load), UI
PtrXplPalette, PtrXplHeader, PalettePcx[]Explosion / PCX palettesResources
IdxPalette, LastIdxPaletteCurrent/previous palette indexResources
PtrNuancesGouraud shading ramp
Shadow, ShadowX/Y/Z, ShadowCol, ShadowLevelDrop-shadow state
AllCameras, FollowCamera, FollowCamBaseDistCamera modeWorld
VueCamera, FlagCameraForcee, AddBetaCam, CameraZone, DefVueDistance[], DefAlphaCam[]Camera view / forced camGameplay
ScaleFactorSprite, SpriteX, SpriteYSprite display transform
CinemaMode, TimerCinema, LastYCinema, DebCycleCinema, DureeCycleCinemaCutscene letterbox framingOrchestration
FlagFade, FlagPal, FadeMenu, FlagBlackPal, FlagShadeMenu, FlagMessageShadePalette fade / shade flagsUI
FlagRain, RainEnable, FlagWater, FlagDrawHorizon, DetailLevelEnvironment effects / LOD
DrawZVObjetsDebug bounding-box draw
ListIncrustDisp[]On-screen incrust overlaysUI

World / cube transform (~25)

GlobalRoleCross-boundary readers
NumCube, NewCubeCurrent / requested cube idGameplay, Rendering, Orchestration
Island, PlanetCurrent island / planetGameplay, Rendering, Audio
WorldXCube, WorldYCube, WorldZCubeWorld-space cube originRendering, Gameplay
CubeStartX/Y/Z, StartXCube/Y/Z, SceneStartX/Y/ZCube / scene entry originsGameplay, Rendering
NewPosX/Y/ZPending teleport positionGameplay
PhantomX, PhantomY, PhantomIslandDeath-cube offsetsGameplay
XpOrgw, YpOrgwWorld origin projected to screenRendering
Nxw/Nyw/Nzw, SaveNxw/.../Nzw, OldX/OldY/OldZSpatial working / delta scratchRendering, Gameplay
TabAllCube[]All-cubes layout tableResources, Gameplay

Resources / assets (~30)

GlobalRoleCross-boundary readers
HQR_Anims, HQR_Bodys, HQR_ObjFix, HQR_SamplesHQR archive handlesGameplay, Rendering, Audio
HQRPtrSprite, HQRPtrSpriteRaw, HQRPtrAnim3DSExtra-object HQR handlesRendering
PtrZvExtra, PtrZvExtraRaw, PtrZvAnim3DSExtra-object bounding volumesRendering, Gameplay
SpriteMem, SpriteRawMem, Anim3DSMem, SampleMem, AnimMem, BodyMem, ObjFixMem, IsleObjMem, CubeInfosMem, ListDecorsMem, MapPGroundMem, ListTexDefMem, MapSommetYMem, MapIntensityMem, SCCMemMemory-pool budgets
PtrScene, PtrSceneMemScene bufferGameplay, Rendering
BufferTexture, AnimateTexture, BufferAnim3D texture/anim buffersRendering
BufferPof, BufferFile3D, BufferBrickModel / brick load buffersRendering
ListAnim3DS3DS animation listRendering
LbaFontLoaded font bufferUI

Audio (~18)

GlobalRoleCross-boundary readers
SampleAmbiance[], SampleRepeat[], SampleRnd[], SampleFreq[], SampleVol[]Ambiance scheduler configGameplay (writes per cube)
SamplePlayed, TimerNextAmbiance, SecondEcart, SecondMinAmbiance scheduling stateGameplay
CubeJingle, SampleAlwaysMovePer-cube jingle / always-on sampleWorld, Gameplay
SampleVolume, VoiceVolume, MasterVolume, SamplesEnableMix levels / enablePlatform, Orchestration (save)
ParmSampleVolume, ParmSampleDecalage, ParmSampleFrequenceSaved sample paramsOrchestration (save)
RestartMusic, BufSpeakMusic restart / speech bufferUI
ReverseStereoStereo channel swapPlatform

The ambiance scheduler is the clearest example of the seed taxonomy being wrong: it is gameplay scheduling audio playback. Splitting it out names the gameplay↔audio coupling instead of burying it in Platform.

Platform / IO (~15)

GlobalRoleCross-boundary readers
MyKeyRaw input scancodeGameplay, UI
LastInputWasKeyboardLast-input-device flag (KEYBOARD.CPP)UI (save menu)
VideoFullScreen, DisplayFullScreenDisplay modeRendering
TimerProtoTimingOrchestration
GamePathname, OldGamePathname, PathFlaData / video pathsResources
FlaFromCD, FlagPlayAcf, MessageNoCD[]CD / video playbackUI
Version, BuildInfo, DistribVersion, Version_US, PlayerNameVersion banners / player idUI, Orchestration

UI / text (~15)

GlobalRoleCross-boundary readers
NumObjDial, DialNbLine, NumObjSpeak, FlagSpeakDialog / speech presentationGameplay, Audio
BufText, BufOrderDialog text / ordering buffers
GameChoice, GameNbChoices, GameListChoice[]Menu selection stateOrchestration
GameOptionMenu[], FlagMenuMouse, FlecheForceeMenu options / cursorPlatform
NewGameTxt[], PleaseWait[]UI strings
String[], ValueShared string-format / script scratchAll (cross-cutting)

Orchestration / lifecycle (~25)

GlobalRoleCross-boundary readers
GameInProgressIn-game vs menu modeGameplay, Rendering, UI
CubeMode, LastCubeMode, ModeLabyrintheInterior/exterior/labyrinth scene modeRendering, World, Gameplay
FlagChgCube, FlagLoadGame, FlagTheEndTransition / load / end triggersWorld, Gameplay
FirstTime, FirstLoop, FirstSaveFirst-iteration guards
SavingEnable, NumVersion, ChecksumSave enable / format version / checksum
DemoSlide, FlagSlideShow, DebugFpsDemo / debug modesRendering
SnapshotRequested, SnapshotNamePolyrec dev harness

The shared bus — worst offenders

These are read by three or more domains. They are the heart of why the engine does not fit in one's head, and they tell you exactly which axes any module split must negotiate:

GlobalDomains touchedWhat it really is
ListObjet[]Gameplay, Rendering, World, UI/AudioThe entity table — the single most-shared structure
NumCube / NewCubeWorld, Gameplay, Rendering, OrchestrationWhich cube we are in / going to
Island / PlanetWorld, Gameplay, Rendering, AudioWhich world we are in
WorldXCube/Y/ZWorld, Rendering, GameplayThe world-space origin everything projects against
CubeModeOrchestration, Rendering, World, GameplayInterior vs exterior — flips the whole render/sim path
PtrPal / PtrPalNormalRendering, Resources, UIThe active palette
ComportementGameplay, Orchestration, (UI)The hero's current behaviour
GameInProgressOrchestration, Gameplay, Rendering, UIIn-game vs menu — the mode switch
String[]AllShared scratch buffer; resists single ownership

The pattern: the offenders are coordinates (cube/island/world origin), the entity table, the palette, and the mode switches (in-game vs menu, interior vs exterior). Those four clusters are the negotiation surface for any real boundary.

Dead / vestigial globals

Surfaced while mapping; candidates for a later cleanup pass (verify before removing):

GlobalObservation
BodyComportementDeclared in C_EXTERN.H:4; no definition or read site found in SOURCES/LIB386.
NbPolyPointsUnder the LIB_SVGA comment (C_EXTERN.H:175); no live def/use — likely dead or ASM-only.
FlagPalettePcxExtern-declared (C_EXTERN.H:119) but its GLOBAL.CPP definition is commented out.

Anti-patterns this map makes visible

  • The global-state bus itself. C_EXTERN.H / GLOBAL.CPP are a ~200-entry shared blackboard. Every domain boundary above is violated by something reaching across it. This is the root smell; the rest are symptoms.
  • God object — T_OBJET (SOURCES/DEFINES.H) and the T_OBJ_3D it wraps (LIB386/H/OBJECT/AFF_OBJ.H). One struct carries position, orientation, body/anim, animation frames, bounding box, AI state, and collision. Every system pokes the parts it cares about, so the struct couples gameplay, rendering, world, and collision into a single type — the data-side equivalent of the global bus.
  • God functions — AffScene and ChangeCube (both SOURCES/OBJECT.CPP). AffScene orchestrates the whole exterior render; ChangeCube tears down and rebuilds world, resources, audio, and entity state on every cube transition. Each reaches across every domain in this map. ChangeCube's phases are decomposed in LIFECYCLES.md — they tour the domains in dependency order, which is the sequence any World/cube facade must preserve.

How a clean engine would draw these lines

Two reference points, for contrast — not as a refactor mandate:

  • Doom 3 / idTech 4 "no god classes." id's discipline was that no single type owns everything; subsystems (renderer, sound, game, file system) sit behind narrow interfaces and talk through them, not through shared globals. The equivalent move here is the platform seam already in flight (PR #284–#286): a C-linkage port with a host stub behind it. Generalised, every domain box above wants the same treatment — a facade (Sort_Insert(...), World_CurrentCube()) that callers go through instead of touching the globals directly. The facade becomes the contract; the globals become its private state.
  • ECS, for the simulation layer. In an entity-component-system the domains above are the components, and a system declares which components it reads and writes in its signature. The question this whole document answers by hand — "what global state does this touch?" — becomes mechanical and local. T_OBJET is the photographic negative of that: it is every component welded into one struct, on one global array.

The realistic path for a behaviour-faithful port is neither rewrite: it is strangler-fig. Pick the busiest boundary — World / cube transform, by the offender table above — wrap its globals behind a facade without moving them, prove behaviour is unchanged with the polyrec/projrec harness, and repeat. This map is the input to choosing which boundary to draw first.

Open questions for the next pass

  • Should persistence get its own annotation column (savegame yes/no) rather than a prose note? ~50 globals are save state and it cross-cuts every domain.
  • CubeMode and GameInProgress are mode switches read everywhere — do they belong in Orchestration, or is there a distinct mode/state-machine context hiding here?
  • Nxw/Nyw/Nzw and OldX/Y/Z are spatial scratch shared by World and Rendering — owner is genuinely ambiguous; confirm by usage before trusting the World assignment.
  • Counts are approximate (clusters collapsed to single rows). A future pass could pin an exact owner per individual global if the facade work needs it.

Three orthogonal views of the same engine — read together:

  • This docstructure: which domain owns which global.
  • ENGINE_GAME_SEAM.mdlayer: engine vs game vs platform, validated against the LBA1 source.
  • LIFECYCLES.mdtime: main-loop frame order, ChangeCube phases, object/anim/behaviour state machines. The shared-bus offenders above (ListObjet, CubeMode, Comportement) are exactly its lifecycle pivots.