Engine / game / platform seam

June 4, 2026 · View on GitHub

Status: DRAFT / living document (v0). Companion to ARCHITECTURE_GLOBALS.md. That doc cuts the LBA2 globals into eight domains (a within-game decomposition). This doc cuts along the deeper, orthogonal axis — engine vs game vs platform — using the sibling LBA1 source as an empirical oracle. Goal: make visible which code is reusable across titles, so strangler-fig boundaries can be drawn where they pay off twice.

Why this axis, and why the module level

The engine/game line matters because of a real prospect: the lba1-classic community source is the LBA1 original Adeline source (Relentless, 1994) — same lineage as this repo, same LIB386/+SOURCES/ layout, same HQR format. One day LBA1 content could run on this engine. Even before then, LBA1 is useful now: it tells us empirically where the engine/game line runs.

The naive way — grep LBA1 for each of the ~200 LBA2 globals — fails: LBA1 is C with different identifiers, so per-global name matching is mostly misses. The robust unit is the module. Modules survive renaming (LBA1 BUBSORT → LBA2 SORT; LBA1 HOLOMAP → LBA2 HOLO*), and a three-way module diff across the trees separates the layers cleanly.

The three trees

TreeGameEraLanguage
lba1-classicLBA1 (Relentless)1994C + ASM
lba2-classicLBA2 (original Adeline)1997C++ + ASM
lba2-classic-communityLBA2 (this fork)1997 + portC++98 + ASM, SDL3

Reproduce the diff with comm over the ext-insensitive module stems of each tree's SOURCES/ (and compare LIB386/ submodule names).

LIB386 kernel evolution

The engine kernel is recognizable across all three, renamed and consolidated:

LBA1LBA2 / communityRole
LIB_3D3DProjection, rotation, matrices
LIB_SVGASVGARasteriser / framebuffer
LIB_SYSSYSTEMHQR, files, keyboard, mouse, timer
LIB_MENUMENUMenu primitives
LIB_MIX / LIB_SAMP / LIB_MIDIail / AILAudio (mixing, samples, music)
LIB_CD(folded into system/audio)CD audio
ANIM, OBJECT, pol_workLBA2 engine growth (anim interp, object display, polygon fillers)
SNAPSHOT, VIDEO_AUDIO_RESAMPLE, libsmackerCommunity port infrastructure

The kernel evolved (more 3D, polygon fillers split out, audio unified behind ail) but the spine — projection, raster, system/HQR, sound — is continuous.

SOURCES module layers (the three-way diff)

Layer 1 — durable engine / framework core (in all three)

AMBIANCE COMMON C_EXTERN DEFINES DISKFUNC EXTRA FICHE FIRE FUNC GAMEMENU GERELIFE GERETRAK GLOBAL GRILLE INCRUST MESSAGE OBJECT PERSO VERSION

These 19 modules survived 1994 → 1997 → 2024. They are the engine-game framework:

Module(s)RoleEngine mechanism vs game content
GERELIFE, GERETRAK, FUNCLife/Track script VM and its opcode functionsThe membrane. Engine interprets; the scripts are game.
OBJECT, PERSO, EXTRA, FICHEEntity / character / projectile / object-data frameworkEngine framework; the behaviours and stats are game content.
GRILLEBrick-isometric world / scene renderEngine; the specific scenes are content.
DISKFUNCResource loading (HQR)Engine.
MESSAGEDialog / speech presentationEngine machinery; the text is content.
GAMEMENUMenu systemEngine machinery; the menu set is content.
AMBIANCEAmbient-audio schedulerEngine mechanism; the per-cube sample tables are content.
INCRUST, FIREOverlays / fire effectEngine rendering effects.
COMMON, C_EXTERN, DEFINES, GLOBAL, VERSIONTypes, globals, version scaffoldingFramework scaffolding (the global bus lives here).

Layer 2 — LBA2 additions (engine evolution + LBA2 game)

In lba2-classic but not lba1-classic. Two sub-kinds:

  • Engine evolution: SORT (the sort tree — LBA1 used BUBSORT), POF (3D model format), ZV (bounding volumes), BEZIER, ANIMTEX, DEC/DEC_XCF (decor), EXTFUNC/PTRFUNC (script function tables), INPUT/JOYSTICK/KEYB (input split out), MEM, SAVEGAME, CONFIG, FLOW, PLAYACF (video), SCAN, VALIDPOS.
  • LBA2 game content: BUGGY, WAGON, DART, INVENT, CHEATCOD, COMPORTE, IMPACT, MESSTWIN, and the holomap (HOLO, HOLOBODY, HOLOGLOB, HOLOPLAN — LBA1's single HOLOMAP re-grown). (HERCULE was grouped here in v0 but the per-module pin reclassifies it as platform / dormant — the Hercules debug display, not game content.)

Layer 3 — community / port additions (in this fork only)

ASSET_PREFLIGHT AUDIO_BALANCE BUILD_INFO COMPRESS CONTROL COPY CREDITS_PARSE DEMO_SEED DIRECTORIES EMBEDDED_CFG_WRITE EXIT_SCREEN FLOW_A FOLLOWCAM_CFG GAMEMENU_MOUSE GRILLE_A HERCUL_A HQR_NAMES IMAGE_LOAD INITADEL PERFTRACE PLASMA RES_DISCOVERY RES_MENU RES_PICKER RES_SWITCH SAVEGAME_LOAD_BOUNDS SAVENAME_VALIDATION TOUCH_INPUT

Almost entirely platform / port: resource discovery (RES_*, DIRECTORIES, HQR_NAMES), de-ASM'd primitives (COPY, COMPRESS, *_A C ports of old ASM), the test harness (CONTROL, PERFTRACE), input/IO (TOUCH_INPUT, IMAGE_LOAD), with a few QoL features (GAMEMENU_MOUSE, FOLLOWCAM_CFG, AUDIO_BALANCE, PLASMA). The fork has not touched the engine core or the game — it has been building the platform layer underneath.

LBA1-only (modernized away by LBA2)

ADFLI_A BALANCE BUBSORT CPYMASK FLA FLIPBOX HOLOMAP MCGA MIXER PLAYFLA — old platform tech (MCGA display, FLA/PLAYFLA video, MIXER/BALANCE audio, BUBSORT). Confirms the churn between titles was mostly platform, not game logic.

Per-module label table (pinned, v1)

The layer narrative above is grouped and v0; this section pins every module with a concrete label, resolving the caveat "pin per-module before relying on a tag." It uses two axes, because they are genuinely orthogonal:

  • Layerengine (reusable Adeline substrate), game (LBA2-specific logic/content), platform (host/OS/port abstraction). engine·platform marks a module that is split (an engine mechanism with a platform-specific tail).
  • Statuslive (built into the shipping binary and reached) or dormant (present in the tree but not built or not called). The directory layout does not encode this — a reusable engine codec (DEC_XCF) can be dormant, while a platform shim is live. The cross-title work surfaced the dormant axis: code one game exercises and another doesn't is engine heritage, not cruft (see the SDK timeline note below).

Verification basis: SOURCES/CMakeLists.txt built sets (SOURCES_FILES, …_ASM, …_WIN/_MACOS are linked; …_DOS, …_ASM_DOS are not), plus a no-caller / not-included check for each module. ASM↔CPP rule below.

ASM vs CPP. Every LIB386/**/*.ASM and SOURCES/*.ASM is the original Adeline assembly, kept as reference; the live code is the same-named .CPP port. Exceptions where both are dormant: COPY, DEC, DEC_XCF, HERCUL_A, KEYB.

LIB386 — the engine kernel + platform backends

ModuleLayerStatusRole
3DengineliveProjection, rotation, matrices, camera
ANIMengineliveAnimation frames + interpolation
OBJECTenginelive3D object display (AFF_OBJ)
pol_workenginelivePolygon fillers (flat/gouraud/textured/zbuf/fog)
SVGAengine·platformliveRasteriser (engine) + mode/screen setup (platform)
SYSTEMengine·platformliveHQR + file format (engine); keyboard/mouse/timer/IO (platform)
MENUenginedormantMenu primitives — not wired into the build (no add_subdirectory); superseded by SOURCES/GAMEMENU. Has equivalence tests.
AIL/ (interface)engineliveAudio abstraction (samples/music/mixing)
AIL/SDLplatformliveActive audio backend (SDL3)
AIL/NULLplatformliveNull audio backend (stub)
AIL/MILESplatformdormantDOS Miles Sound System backend
FILEIOplatformliveFile IO + SAVEPNG (port infra)
libsmackerplatformliveVendored Smacker decoder (LGPL) — LBA2 cinematics (built when MVIDEO_BACKEND=smacker, the default)
SMACKERplatformdormantAdeline Smacker wrapper subdir — not wired into the build
VIDEO_AUDIO_RESAMPLEplatformliveAudio resample for video (port infra)
SNAPSHOTplatformliveScreenshot / test-harness capture
HShared headers / type + struct contracts

SOURCES — engine framework core (present in all three trees)

Module(s)LayerStatusRole
GERELIFE, GERETRAK, FUNC, EXTFUNCengineliveLife/Track script VM + opcode tables — the membrane
OBJECT, PERSO, EXTRA, FICHEengineliveEntity / main-loop / projectile / object-data framework
GRILLEengineliveBrick-isometric world / scene render
DISKFUNCengineliveHQR resource loading
MESSAGE, INTEXTengineliveDialog / speech / in-game text presentation
GAMEMENUengineliveMenu system
AMBIANCEengineliveAmbient-audio scheduler
INCRUST, FIRE, FLOW, PLASMA, RAINengineliveOverlay / particle / effect rendering
PATCHengineliveRuntime patch of compiled script/scene pointers
MEMengineliveMemory management
LZSS, COMPRESSengineliveCompression (LZSS / LZMIT)
MUSICengine·platformliveMusic playback (engine logic + backend)
COMMON, C_EXTERN, DEFINES, GLOBAL, VERSIONengineliveTypes, globals, version scaffolding (the shared bus)

SOURCES — engine evolution (LBA2-added, engine-grade)

Module(s)LayerStatusRole
SORTengineliveSort tree (LBA1 used BUBSORT)
POF, ZV, BEZIERenginelive3D model format / bounding volumes / curves
ANIMTEXengineliveAnimated textures
SCANengine·platformliveKey/bit-scan input helpers
VALIDPOSengineliveCollision / position validation
FLOW_A, GRILLE_AengineliveC ports of former flow / grille ASM
3DEXT/ (TERRAIN, DRAWSKY, DECORS, LOADISLE, BOXZBUF, LINERAIN, MAPTOOLS, GLOBEXT, LBA_EXT, VAR_EXT)engineliveExterior 3D: terrain, sky, decor, island load, z-buffer
INITADELengineliveEngine init
PLAYACFengine·platformliveVideo playback — now Smacker (its XCF codec DEC_XCF is dormant)
SAVEGAMEengine·gameliveSave system (engine mechanism + LBA2 save layout)
CONFIGengine·platformliveConfig load/store

SOURCES — LBA2 game content

Module(s)LayerStatusRole
INVENTgameliveInventory
BUGGY, WAGON, DARTgameliveVehicles / minigames
COMPORTEgameliveBehaviours
CHEATCODgameliveCheat codes
IMPACTgameliveImpact scripts
HOLOBODY, HOLOGLOB, HOLOPLANgameliveHolomap (LBA1's single HOLOMAP, re-grown)
MESSTWINgameliveTwinsen chapter-message data tables
CREDITS, CREDITS_PARSEgameliveEnd credits

SOURCES — platform / port layer (this fork)

Module(s)LayerStatusRole
CONTROL, PERFTRACE, DEMO_SEEDplatformliveTest harness / determinism / perf trace
RES_DISCOVERY, RES_MENU, RES_PICKER, RES_SWITCH, DIRECTORIES, HQR_NAMESplatformliveResource discovery / data-dir routing
INPUT, JOYSTICK, TOUCH_INPUTplatformliveInput (SDL3 keyboard / gamepad / touch)
IMAGE_LOADplatformliveImage (PCX/etc.) loading
ASSET_PREFLIGHTplatformliveAsset validation at boot
CONSOLE/ (CONSOLE, _CMD, _GIVE, _STATE)platformliveDebug console (CONSOLE_MODULE)
GAMEMENU_MOUSE, AUDIO_BALANCE, EXIT_SCREEN, FOLLOWCAM_CFGplatformliveQoL / port features
SAVEGAME_LOAD_BOUNDS, SAVENAME_VALIDATIONplatformliveSave-path hardening
BUILD_INFO, EMBEDDED_CFG_WRITEplatformliveBuild metadata / embedded default config

Dormant register (present in tree, not in the shipping binary)

The actionable cross-cut. Do not delete without recording the heritage — most of this is the Adeline engine's other-era surface, the evidence that LIB386 is a multi-game SDK.

ModuleWould-be layerWhy dormantHeritage
DEC, DEC_XCF (+.ASM)enginenot built / no callersThe XCF cinematic codec — superseded by Smacker (PLAYACF→libsmacker). Decodes Time Commando .ACF; see TIME_COMMANDO learning notes.
HERCULE, HERCUL_A (+.ASM)platform…_DOS set, not builtHercules mono adapter (0xB0000) — second-monitor debug display
KEYB (+.ASM)platform…_ASM_DOS, not builtDOS keyboard handler — replaced by INPUT/SDL3
CRITICALplatform…_DOS set, not builtDOS critical-error handler
COPY (+.ASM)platformnot built, source /*-wrappedFrame-copy primitives — library is already pitch-aware
CONFIG/ (MAIN, AFFKEY, …)platformseparate tool, no CMake targetOriginal Adeline standalone config utility
AIL/MILESplatformbackend not selectedDOS Miles Sound System audio backend
LIB386/MENUengineno add_subdirectoryAdeline menu primitives — superseded by SOURCES/GAMEMENU; has equivalence tests
LIB386/SMACKERplatformno add_subdirectoryAdeline Smacker wrapper subdir (distinct from the live vendored libsmacker)
all *.ASM originals(matches .CPP)reference, not assembledOriginal Adeline x86 — the .CPP port of the same name is live

LIB386/MENU and LIB386/SMACKER are the two modules the Phase-0 restructure RFC (discussion #120, open question 3) flags for a preserve/delete/revive decision. This register is the data for that call: the default is preserve — dormant code here is the engine's other-era / other-game surface, not cruft (DEC_XCF decoding Time Commando's videos is the clearest case).

Version / dual-path seams (the SDK timeline)

Several live modules carry two code paths for one job because the Adeline formats evolved across titles (verified against LBA1 1994 / Time Commando 1996 / LBA2 1997 retail data):

ModuleDual pathTransition it bridges
LIB386/SYSTEM (LZ)LZSS + LZMITLZMIT added 1994→96 (TC already has it)
LIB386/AIL samplesVOC + RIFF/WAVVOC (LBA1) → WAV (TC, LBA2)
PLAYACFDEC_XCFSmacker (live) vs XCF (dormant)cinematics XCF (TC) → Smacker (LBA2)
POF / body loader16-bit (LBA1/TC) vs 32-bit (LBA2) coordsgeometry widened 1996→97

These seams are the SDK's backward-compatibility made physical. When labelling, a dual-path module is engine whose liveness spans a format version boundary — useful to flag before a facade assumes a single format.

Layer tags on the eight domains

Mapping ARCHITECTURE_GLOBALS.md's domains onto this axis, with the owning module(s) as evidence. Almost every domain is engine mechanism + game content — the split is rarely a whole domain, it runs through each one:

DomainLayerEvidence / note
Gameplay / simulationGame on an engine substrateOBJECT/PERSO/EXTRA framework is engine (all three); behaviours, inventory, magic, stats are LBA2 content driven by the script VM.
RenderingEngine3D/SVGA/GRILLE/INCRUST in all three; SORT/pol_work are LBA2 engine evolution.
World / cube transformEngineGRILLE + DISKFUNC world model in all three; specific cube/island data is content.
Resources / assetsEngineHQR (LIB_SYSSYSTEM) + DISKFUNC in all three; the archives are content.
AudioEngine mechanism + game schedulingAMBIANCE in all three; mixing via ail; per-cube sample tables are content.
Platform / IOPlatformSYSTEM/SVGA + the community port layer (CONTROL, RES_*, TOUCH_INPUT). This is the PAL.
UI / textEngine machinery + game contentMESSAGE/GAMEMENU in all three; the menus, text, holomap are content.
Orchestration / lifecycleEngine frameworkMain loop / scene lifecycle / INITADEL; the specific game flow is content.

What this means for prioritisation

  • The script VM (GERELIFE/GERETRAK/FUNC) is the keystone seam. It is the literal engine/game interface, present in all three trees. The dynamic proof is in LIFECYCLES.md: the per-frame main loop crosses the membrane at step 3 (DoTrack, track scripts) and step 6 (DoLife, life scripts) for every object — the engine drives the loop, the game's scripts are invoked. A clean boundary here is what makes the engine title-agnostic — the highest-leverage facade, aligned with the Life/Track tooling thread (#181). Full API spec: ENGINE_GAME_INTERFACE.md.
  • Engine-seam facades pay off twice. Drawing a facade around an all-three module (World/cube via GRILLE+coords, Resources via HQR/DISKFUNC) cleans up LBA2 now and is reusable for LBA1 later. Prefer these over facades around Layer-2 game content (INVENT, magic ball), which differ per title by definition.
  • The platform seam is already on this path. The community layer (Layer 3) is the PAL; PR #284–#286 formalise it. Continuing to push platform concerns into that layer keeps the engine core clean for both titles.

Caveats

  • Renaming and language. LBA1 is C; modules and identifiers were renamed and reorganised for LBA2 (BUBSORTSORT, HOLOMAPHOLO*, LIB_3D3D). Module presence is evidence of a shared concept, not drop-in compatibility.
  • Format and capability divergence. LBA1 used older formats (FLA video, MCGA) and a more limited 3D path. Hosting LBA1 here means bringing its content onto this newer engine, not merging two equals.
  • twin-e / TwinEngine already runs both titles via reimplementation — feasibility is proven. The original-source path is harder but preservation-faithful. The concrete per-subsystem porting cost is assessed in LBA1_PORTING_SURFACE.md.
  • The domain-level tags here are coarse; the per-module label table pins each module (layer + live/dormant) and is the authority for a specific facade. It also adds the dormant axis the layer narrative omits.

This map is one of three orthogonal views of the same engine; use them together:

  • ARCHITECTURE_GLOBALS.mdstructure: which domain owns which global (the shared-bus offenders).
  • This doclayer: engine vs game vs platform (what is reusable across titles).
  • LIFECYCLES.mdtime: when state is created, mutated, destroyed (main-loop frame order, the ChangeCube phases, object/anim/behaviour state machines).

And the data contract underneath all three:

  • ENGINE_FILE_FORMATS.md — the on-disk formats the engine reads (HQR, LZ, body, anim, sprite, samples, XCF, …), at engine altitude, with the cross-title version timeline. The dormant/dual-path seams in the per-module table are the codecs that doc specs.

The temporal axis sharpens this one twice over:

  • The main-loop frame order shows the engine/game membrane in motion — track scripts at step 3, life scripts at step 6, every frame, per object.
  • ChangeCube's six phases are a tour through the domains in dependency order (grid → objects → zones → CubeMode → camera). That sequence is the spec a World/cube facade must preserve: it tells you not just what the boundary owns but the order in which a cube transition crosses it.