==============================================================================
DESCRIP.TXT - the commentary for DESCRIP.MMS
==============================================================================
ScummVM 1.8.1 Kyra + Sierra (AGI/SCI) extract, OpenVMS 8.4 Alpha.
Builds KYRA.EXE and SIERRA.EXE.  See also README.VMS and VMS_CHANGES.TXT.

WHAT THIS FILE IS
-----------------
DESCRIP.MMS used to carry ~1100 lines of comment around ~450 lines of build
rules.  All of that commentary now lives HERE, in the SAME ORDER as the rules it
describes, and DESCRIP.MMS itself is bare.  Nothing was rewritten or summarised
in the move - this is the original text.

HOW TO READ IT
--------------
Prose is at the left margin.  The actual DESCRIP.MMS lines each block describes
are echoed VERBATIM, INDENTED BY FOUR SPACES, immediately after their commentary:

        this is commentary
            CXXFLAGS = /name=(as_is,shortened)/float=ieee-       <- build file

So you can read this file top to bottom as an annotated DESCRIP.MMS.  The
indented lines are a COPY for orientation, NOT the build file - if you change a
rule, change it in DESCRIP.MMS.  If you change WHY a rule exists, change it here.

DO NOT PUT COMMENTS BACK IN DESCRIP.MMS.  Keeping the reasoning here and the
rules there is deliberate; the two files are meant to be read side by side, and a
comment that exists in only one of them will be the one that goes stale.

A WARNING THIS FILE INHERITS
----------------------------
Several blocks below record HP C++ Alpha back-end crashes and the exact flag that
works around each one.  They are the most load-bearing text in this port.  Before
changing any per-file compile rule, read the block for that file AND the boxed
warning in the "Files needing /NOOPTIMIZE" section - /NOOPTIMIZE is not the safe
end of the scale and has itself caused a crash in this tree.
==============================================================================

DESCRIP.MMS - Standalone ScummVM 1.8.1 engine loaders for OpenVMS
============================================================================
Target : OpenVMS 8.4 Alpha (AXP), HP C++ compiler, DECwindows (X11) + SDL 1.2
Use    : MMK  (or MMS)   run from THIS directory (the extracted [...]SRC] root)
Result : TWO no-launcher, no-sound loaders that auto-detect the game in the
         CURRENT (default) directory and run it, exactly like the FreeSCI /
         Sarien OpenVMS loaders:
           KYRA.EXE   - Kyra (LoK/HoF/MR) + LoL + EOB/EOB2
           SIERRA.EXE - Sierra AGI (+ preAGI) and SCI 0/1/1.1 (+ SCI2/SCI32)

No audio device is ever opened (NullSdlMixerManager). Links only SDL 1.2 + X11.

----------------------------------------------------------------------------
WHY TWO EXECUTABLES AND FOUR OBJECT LIBRARIES

The two images SHARE every non-engine object (audio, backends, common,
graphics, gui, image, video and the generic engines/*.cpp) - those are compiled
ONCE.  Only base/plugins.cpp is compiled twice, into
  [.base]plugins.obj         -> registers KYRA   (KYRA.EXE)
  [.base]plugins_sierra.obj  -> registers AGI+SCI (SIERRA.EXE), selected by the
                                extra define STANDALONE_TARGET_SIERRA, which
                                engines/plugins_table.h switches on.
ENABLE_KYRA / ENABLE_AGI / ENABLE_SCI / ENABLE_SCI32 are ALL defined for every
compile (see $(DEFS)); the per-image selection happens ONLY in plugins_table.h.
That is deliberate - it keeps ONE object per source file, so adding SIERRA.EXE
did not invalidate the existing 358 KYRA.EXE objects (a full rebuild of all 482
is ~8 HOURS on the
es40 emulator).  It is safe because the three ENABLE_* macros are referenced
almost exclusively inside [.engines...]; the only exceptions are listed under
"OBJECTS THAT MUST BE DELETED" below.

FOUR object libraries, not one, because an OpenVMS object library keys modules
by the object's FILE NAME AND IGNORES THE DIRECTORY.  The union of kyra + sci +
agi + the common tree has 22 colliding basenames (console, detection, font,
saveload, util are three-way; also adlib, cms, debugger, error, file, menu,
message, object, picture, quicktime, resource, screen, script, sound,
sound_midi, text, view).  LIBRARY/INSERT silently keeps only one of each, so a
single library would drop ~22 modules and produce a wall of %LINK-W-NUDFSYMS.
Splitting by engine removes the problem structurally:
  SCUMM_MAIN.OLB - 245 shared modules (everything except the three engines)
  KYRA_ENG.OLB   - 109 [.engines.kyra] modules
  SCI_ENG.OLB    -  82 [.engines.sci...] modules
  AGI_ENG.OLB    -  41 [.engines.agi] modules
Verified: kyra, sci and agi each have ZERO internal basename collisions, and
SCUMM_MAIN has exactly TWO (common+gui error, audio+common quicktime) - those
two keep the old "one member of the pair goes on the LINK command line" fix.
sci vs agi DO collide on 6 names (console detection font menu picture view) but
they live in DIFFERENT libraries, so both stay reachable in SIERRA.EXE.

EACH LIBRARY IS LISTED TWICE ON THE LINK COMMAND LINE (main, engines, main,
engines).  The OpenVMS linker searches a library only when it reaches it: a
module extracted from SCI_ENG.OLB may need a Common:: symbol whose module has
not been extracted from SCUMM_MAIN.OLB yet, and the linker will not go back on
its own.  A second pass over each library closes that.  Listing a library twice
is not an error and cannot cause a duplicate extraction - the linker only pulls
a module in to satisfy a still-undefined symbol.

OBJECTS THAT MUST BE DELETED when moving an existing build to this DESCRIP.MMS
(everything else stays valid; do NOT run MMK CLEAN, that costs ~8 hours):
  [.BASE]PLUGINS.OBJ              - engines/plugins_table.h was rewritten
  [.BASE]MAIN.OBJ                 - COSMETIC ONLY, skip it if you like.
    autodetectCurrentDir()'s three messages said "Kyra standalone"; the same
    code serves SIERRA.EXE, so they now say "standalone loader".
  [.IMAGE.CODECS]TRUEMOTION1.OBJ  - image/codecs/truemotion1.h:26 guards the
    WHOLE class on ENABLE_SCI32/ENABLE_ZVISION/DYNAMIC_MODULES, so this object
    is currently EMPTY (0 modules).  With ENABLE_SCI32 it emits real code, and
    it needs image/codecs/truemotion1data.h, which had to be ADDED to the tree.
  [.IMAGE.CODECS]CODEC.OBJ        - codec.cpp:209 gates the DUCK/duck FourCC
    cases on IMAGE_CODECS_TRUEMOTION1_H, i.e. on truemotion1.h having a body.
  [.VIDEO]COKTEL_DECODER.OBJ      - video/coktel_decoder.h:24 guards the whole
    file on ENABLE_GOB/ENABLE_SCI32/DYNAMIC_MODULES, so it too is currently
    EMPTY.  SCI32 needs it: sci/engine/kvideo.cpp:355 and sci/console.cpp:264
    construct Video::AdvancedVMDDecoder.  Expect a LONG compile - 2860 lines.
  KYRA_OBJS.OLB, KYRA.EXE, KYRA.MAP - superseded (KYRA_OBJS.OLB is gone; the
    four new libraries replace it).
@UPGRADE_SIERRA.COM does exactly this list, and checks the new sources arrived.
Those three code objects are the ONLY files outside [.engines...] whose
preprocessing changes - established by grepping the whole tree for
ENABLE_SCI32 / ENABLE_SCI / ENABLE_AGI.  graphics/sjis.h:29 also names
ENABLE_SCI but its guard is already satisfied by ENABLE_KYRA, so it is unchanged.

----------------------------------------------------------------------------
WARNING - CHANGING $(OPT) REBUILDS NOTHING BY ITSELF

  AFTER CHANGING THE OPTIMIZATION LEVEL, RUN:  MMK CLEAN  THEN  MMK/IGNORE=WARNING

MMK does NOT make DESCRIP.MMS a prerequisite of the objects it builds, so raising
$(OPT) from LEVEL=3 to LEVEL=4 leaves every existing .OBJ exactly as it was and
MMK will report the tree up to date.  You would get a "full optimization" build
description and a LEVEL=3 image.  Nothing warns you.

So the LEVEL=4 change is one of the few things that genuinely does require the
full rebuild.  That is affordable now: the ~8-hour figure quoted throughout this
file is EMULATED time on es40, and it is the reason the standing advice here is
"never MMK CLEAN".  On real AlphaServer hardware that advice is much weaker -
weigh it against the fact that a partially-rebuilt tree silently mixes objects
compiled at two different optimization levels.  Mixing levels is not unsafe (it
is not an ABI or layout change, unlike a header edit), it just means the parts
you did not rebuild stay slow.

----------------------------------------------------------------------------
WARNING - HEADER EDITS AND STALE OBJECTS

  AFTER EDITING ANY HEADER NOT LISTED IN $(GLOBAL_HDRS), RUN:  MMK CLEAN

Header dependencies here are PARTIAL: $(GLOBAL_HDRS) (see the "Header
dependencies" section) covers only the handful of headers that nearly every TU
includes AND that have needed OpenVMS port edits.  Any other header edit
recompiles NOTHING, and MMK will cheerfully report the tree up to date.

This is not theoretical.  With no dependencies at all, a rework of
common/singleton.h left ~50 objects compiled against the OLD header while a
handful were rebuilt against the new one; the result linked with 11 undefined
"Common::Singleton<X> ::_singleton" symbols and cost hours to diagnose, because
every individual file "looked" correct - only the MIX was wrong.  A second
stale object (chroot-fs-factory.cpp, missing a FORBIDDEN_SYMBOL_EXCEPTION_getenv
that its siblings had) had been hiding the same way and only surfaced on the
first clean rebuild.  When in doubt, MMK CLEAN - a full rebuild is slow but it
is the only state that is trustworthy.
----------------------------------------------------------------------------

----------------------------------------------------------------------------
IMPORTANT - INCLUDE-PATH DESIGN (differs from FreeSCI):

  FreeSCI compiles each file after SET DEFAULT [.subdir] and uses per-directory
  relative include paths.  ScummVM CANNOT be built that way: its sources use
  TREE-ROOT-RELATIVE quoted includes, e.g.
        #include "common/system.h"
        #include "engines/kyra/screen.h"   (engine sources - see the note below,
                                           upstream writes "kyra/screen.h")
  plus a handful of SAME-DIRECTORY bare includes, e.g. dosbox.cpp does
        #include "dbopl.h"

  Therefore EVERY file is compiled while the default directory stays at this
  SRC root, with ONE uniform include path:
        /INCLUDE=([], SDL)
    []          -> resolves ALL tree-root-relative includes: "common/...",
                   "gui/...", "backends/...", "audio/...", "graphics/...",
                   "image/...", "video/...", "engines/...", "config.h", and
                   the engine sources' "engines/kyra/..." includes.
    SDL         -> the SDL 1.2 headers logical (same one FreeSCI uses)

  WHY NO SEPARATE ENGINE INCLUDE BASE (was [.engines] / SCUMMENG:) :
  Upstream ScummVM engine sources include each other as "kyra/screen.h", but on
  this tree the kyra sources physically live at [.engines.kyra].  A subdirectory-
  qualified quoted include cannot be satisfied by a relative search dir on VMS:
  CXX turns "kyra/screen.h" into RMS filespec [.kyra]screen.h, and merging a
  relative base like [.engines] does NOT stack its subdir level, so it resolves
  to [.kyra]screen.h (never found -> %CXX-F-SRCFILNOOPEN).  A rooted/concealed
  logical was attempted but is unreliable when the tree lives under another
  concealed device (SYS$SYSROOT:).  Since this extract is OpenVMS-ONLY, the
  clean fix was applied at the SOURCE: the 430 "kyra/..." includes across the
  145 [.engines.kyra] files were rewritten to "engines/kyra/..." (root-relative,
  exactly like the 1558 "common/..." includes that always worked).  So [] alone
  now resolves everything and NO SCUMMENG / @SETUP step is needed.
  THE SAME REWRITE WAS APPLIED TO THE SIERRA ENGINES when they were added: 696
  "sci/..." includes across [.engines.sci...] and 164 "agi/..." includes across
  [.engines.agi] became "engines/sci/..." / "engines/agi/...".  If you ever copy
  another file in from a pristine 1.8.1 tree, YOU MUST DO THIS TO IT TOO, or it
  fails with %CXX-F-SRCFILNOOPEN on its first engine-local include.  Note that
  sci has SUBDIRECTORIES (engine, graphics, parser, sound, sound/drivers, video),
  so the rewrite is "sci/engine/state.h" -> "engines/sci/engine/state.h" - keep
  the intermediate levels.

  The 7 same-directory bare includes (dbopl.h/dosbox.h/mame.h in
  [.audio.softsynth.opl]; logo_data.h in [.engines]; editrecorddialog.h /
  recorderdialog.h in [.gui]) are found because DECC searches the directory of
  the source file FIRST for the "" include form - the VMS equivalent of gcc's
  same-directory rule.  This is why the single .CPP.OBJ suffix rule below works
  for all 482 objects without any SET DEFAULT gymnastics.

  This relies on DECC/CXX translating unix-style relative include specs
  ("common/system.h") against a VMS include directory ([]).  Modern DECC on
  8.4 does this by default.  If the compiler rejects the slash form, add
  /INCLUDE_DIRECTORY with DECC$FILENAME_UNIX_REPORT logicals - see README.VMS.
============================================================================

    .IFDEF __ALPHA__
    ARCH = ALPHA
    .ENDIF

----------------------------------------------------------------------------
Compiler / linker
----------------------------------------------------------------------------
    CXX  = CXX
Use CXXLINK (the C++-aware link driver), NOT plain LINK: it pulls in the HP C++
run-time startup that establishes the program transfer address (entry point)
and runs static constructors before main().  Plain LINK of C++ objects yields
%LINK-W-USRTFR "no user transfer address" (image links but won't run).  The
working sibling C++ port (dosbox) uses CXXLINK for the same reason.
    LINK = CXXLINK

Preprocessor defines (validated on Linux with the identical set):
  HAVE_CONFIG_H        -> read []config.h
  SDL_BACKEND          -> select the SDL backend; lets scummsys.h use SDL_endian.h
  POSIX                -> POSIX fs/saves backend (VMS CRTL supplies stat/opendir)
  ENABLE_KYRA=STATIC_PLUGIN, ENABLE_LOL, ENABLE_EOB
                       -> compile Kyra + LoL + EOB. NOTE: several headers
                          (e.g. audio/mods/maxtrax.h) only expose their class
                          bodies when ENABLE_KYRA is defined - do not drop it.
  ENABLE_AGI=STATIC_PLUGIN
                       -> compile Sierra AGI v1/v2/v3 + preAGI (Mickey, Troll,
                          Winnie).  Upstream has no sub-options for AGI.
  ENABLE_SCI=STATIC_PLUGIN
                       -> compile Sierra SCI 0 / 1 / 1.1.  ALSO satisfies the
                          guard in graphics/sjis.h (already satisfied by
                          ENABLE_KYRA, so nothing there changes).
  ENABLE_SCI32         -> compile the SCI2/SCI2.1/SCI3 ("SCI32") support: the
                          7 extra TUs (kgraphics32, controls32, frameout,
                          paint32, palette32, text32, robot_decoder) plus the
                          ~89 extra detection entries and the many in-file
                          #ifdef ENABLE_SCI32 branches.
                          HONEST WARNING: upstream 1.8.1 ships SCI32 as
                          build-by-default = NO (engines/sci/configure.engine:
                          `add_engine sci32 "SCI32 games" no`) and marks the
                          SCI32 detection entries ADGF_UNSTABLE.  SCI2 games
                          will be DETECTED and will START, but most are not
                          playable to the end in 1.8.1.  Each one raises the
                          "not yet fully supported" dialog first
                          (Engine::warnUserAboutUnsupportedGame, engine.cpp:599,
                          reached from advancedDetector.cpp:302); answer
                          "Start anyway".  This was compiled in at the user's
                          explicit request.
  KYRA_STANDALONE      -> our no-launcher / no-sound / fixed-window mode.  The
                          name is historical: it gates the standalone
                          auto-detect-in-current-directory behaviour used by
                          BOTH images, not anything Kyra-specific.
  DECLSPEC= , SDLCALL= -> neutralise Win32-isms in the SDL 1.2 headers (FreeSCI)
  NO_SOUND             -> belt-and-braces; standalone build already mutes audio

NOT in this list, and deliberately so: STANDALONE_TARGET_SIERRA.  It is added on
the command line for the ONE extra compile of base/plugins.cpp (see the
[.base]plugins_sierra.obj rule).  Adding it here would put AGI+SCI into KYRA.EXE
as well.
    DEFS = HAVE_CONFIG_H,SDL_BACKEND,POSIX,-
    "ENABLE_KYRA=STATIC_PLUGIN",ENABLE_LOL,ENABLE_EOB,-
    "ENABLE_AGI=STATIC_PLUGIN","ENABLE_SCI=STATIC_PLUGIN",ENABLE_SCI32,-
    KYRA_STANDALONE,"DECLSPEC=","SDLCALL=",NO_SOUND

    INCS = ([],SDL)

----------------------------------------------------------------------------
OPT - THE OPTIMIZATION SETTING FOR THE WHOLE TREE.  Change it HERE only.
----------------------------------------------------------------------------
This is the single source of truth for the default optimization level; both
$(CXXFLAGS) and $(CXXFLAGS_SIERRA) reference it, so they can no longer drift
apart.  The three de-optimized macros below (NOOPT / OPT1 / NODBG) deliberately
do NOT use it - they exist precisely to override it per file.

2026-08-28 - RAISED FROM LEVEL=3 TO LEVEL=4 (FULL OPTIMIZATION).  The build
target is now REAL AlphaServer hardware, not the es40 emulator, so compile time
is no longer the binding constraint it was while this port was being brought up.

LEVEL=4 IS THE COMPILER'S OWN DEFAULT, i.e. the tree had been pinned BELOW the
default all along.  LEVEL=4 adds, over LEVEL=3, additional inline expansion plus
loop unrolling and code replication to remove branches - which is exactly what
the pixel loops in engines/kyra/screen*.cpp, graphics/scaler/* and the sci/agi
renderers want.  TUNE=HOST tunes scheduling for the machine doing the compile;
that is correct now that you compile and run on the same AlphaServer.
It is also the level the two known-good sibling OpenVMS ports on this system use
(freesci/src/DESCRIP.MMS and sarien/src/DESCRIP.MMS both say LEVEL=4,TUNE=HOST).

ABOUT LEVEL=5 - NOT set here, ON PURPOSE, but easy to try:
  LEVEL=5 adds the loop TRANSFORMATION package (interchange / fusion / blocking,
  software pipelining).  Two reasons it is not the default here.  (1) I could not
  confirm from anything on this system that HP C++ (CXX) V7.3 accepts LEVEL=5 -
  it is documented for HP C, and CXX shares the GEM back end, but an unknown
  qualifier value fails EVERY compile.  Test it in one second before committing
  to it:
      $ CXX /OPTIMIZE=(LEVEL=5,TUNE=HOST)/NOOBJECT/INCLUDE=([],SDL) -
            /DEFINE=(HAVE_CONFIG_H,SDL_BACKEND,POSIX) [.common]util.cpp
  (2) The loop unroller/peeler in this compiler is already a PROVEN crasher in
  this tree - image/codecs/cdtoons.cpp dies in PEEL_LOOP <- GEM_LU_MAIN at
  LEVEL=3 (see its rule at the bottom of this file).  LEVEL=5 leans on exactly
  that machinery harder, so expect more files to need per-file rules if you go
  there.  If the test above passes and you want it, change the ONE line below.

ABOUT /ARCHITECTURE - also not set, and a bigger lever than the LEVEL:
  /ARCHITECTURE=HOST lets the compiler emit instructions specific to the CPU in
  the machine you compile on (EV56 byte/word extensions BWX, EV6 count/float
  extensions CIX/FIX) instead of the generic EV4 baseline.  On a modern
  AlphaServer that is a real win for the byte-at-a-time blitters.  The cost is
  that the image will NOT run on an older Alpha - it will fail with an illegal
  instruction.  If both machines are the same box, append /ARCHITECTURE=HOST to
  the line below; if you might ever run the image elsewhere, leave it alone or
  use an explicit floor such as /ARCHITECTURE=EV56.

THE REAL RISK OF RAISING THE LEVEL IS WRONG CODE, NOT A COMPILER CRASH.
  A crash is loud and lands on a specific file; you read the GEM_* pass name and
  add a per-file rule (that whole taxonomy is at the bottom of this file).  A
  MISCOMPILE is silent at build time and shows up as an ACCVIO at run time, in a
  routine that has nothing to do with the miscompiled one.  This tree has ALREADY
  BEEN BITTEN, at the level we were leaving behind: HP C++ V7.3-009 miscompiles
  the upstream "new ((void *)dst++) Type(*first++);" copy-loop shape at
  /OPTIMIZE=(LEVEL=3,TUNE=HOST) into an INFINITE LOOP - it drops the "first++"
  and hoists a non-invariant exit test out of the loop, so the copy walks forward
  until it hits an unmapped page.  It killed KYRA.EXE on the first Common::Array
  push_back of the process.  The fix is at source (increments moved out of the
  new-expression's operands) in common/memory.h and common/algorithm.h - read the
  long note in common/memory.h before touching either file.

  TWO CONSEQUENCES FOR THIS CHANGE, BOTH HONEST:
  (1) That source fix was verified at LEVEL=3, not at LEVEL=4.  LEVEL=4 unrolls
      and inlines more aggressively, which is exactly the machinery that got the
      loop wrong the first time.  It is more likely to be fine than not - the fix
      removes the construct the optimizer mishandled rather than hiding it - but
      it has NOT been proven at this level.
  (2) If KYRA.EXE or SIERRA.EXE starts ACCVIO-ing at run time after this rebuild
      AND it ran before, SUSPECT THIS CHANGE FIRST, before any source theory.
      Confirm it in one step: rebuild the suspect file with $(CXXFLAGS_OPT1) and
      see if the symptom goes away.  Then get the /MACHINE_CODE listing for the
      routine and read the loop - that is what identified the bug above, after
      runtime evidence alone had failed to separate five competing theories.
----------------------------------------------------------------------------
    OPT = /OPTIMIZE=(LEVEL=4,TUNE=HOST)

/name=(as_is,shortened) : preserve case to match the SDL library (built as_is)
                          and shorten the long C++ mangled external names.
/float=ieee             : match SDL / DECwindows float model.
/WARNINGS=(DISABLE=...)  : these are HP C++ (CXX) message names - NOT the DECC
  (CC) names.  An earlier version copied FreeSCI's CC tags (PTRMISMATCH,
  EMPTYFILE, QUESTCOMPARE, ...); CXX does not know them and emitted one
  %CXX-W-UNKMSGID per tag, which raised the compile to WARNING severity and
  made MMK abort the target.  This set matches the working DOSBox CXX port.
  SHIFTCNTBIG is kept for the 64-bit byte-swap paths in common/endian.h.
  UNRECPRAGMA silences the ~52 Mac/Xcode "#pragma mark" code-folding markers
  that ScummVM sprinkles through the sources; CXX ignores them but warns, and
  the warning alone raises the compile to WARNING severity and aborts MMK.
  MISSINGRETURN: ScummVM's pervasive "switch(...){...default: error(...);}"
  idiom - error() is NORETURN but CXX can't see that, so it warns that control
  may fall off a non-void function.  Harmless; the default case never returns.

  NOTE: warning-severity compiles abort MMK by default.  Because a template-
  heavy C++03 tree like this trips MANY distinct benign CXX warnings, the
  robust build command is:  MMK /IGNORE=WARNING  (real ERRORS still stop it).
  The sibling DOSBox OpenVMS port builds exactly that way (BUILD_MMS.COM).
    CXXFLAGS = /name=(as_is,shortened)/float=ieee-
    $(OPT)/INCLUDE=$(INCS)/DEFINE=($(DEFS))-
    /WARNINGS=(DISABLE=(INTOVERFLOW,SHIFTCNTBIG,CODEUNREACHABLE,UNRECPRAGMA,-
    MISSINGRETURN))

CXXFLAGS_NOOPT : identical to CXXFLAGS but with /NOOPTIMIZE.  A few large /
complex source files crash the HP C++ Alpha back-end optimizer with an ACCVIO
inside CXX$COMPILER (GEM_DF/CSE dataflow pass; observed at /OPTIMIZE=LEVEL=3,
which was the tree default at the time) - a known compiler bug, not a source
error.  Such files get an explicit /NOOPTIMIZE rule below (see the "Files
needing /NOOPTIMIZE" section).  This mirrors the sibling DOSBox OpenVMS port's
per-file NOOPT handling.

THESE RULES SURVIVE THE MOVE TO REAL HARDWARE AND TO $(OPT)=LEVEL=4.  They are
workarounds for deterministic COMPILER BUGS, and a faster machine does not fix a
compiler bug - if anything LEVEL=4 makes those passes work harder.  Do not strip
them because the build host changed.  The one thing that DID change is that
re-testing a file is now cheap: see "RETESTING THE PER-FILE RULES" at the bottom
of this file for the order to try things in.
    CXXFLAGS_NOOPT = /name=(as_is,shortened)/float=ieee-
    /NOOPTIMIZE/INCLUDE=$(INCS)/DEFINE=($(DEFS))-
    /WARNINGS=(DISABLE=(INTOVERFLOW,SHIFTCNTBIG,CODEUNREACHABLE,UNRECPRAGMA,-
    MISSINGRETURN))

CXXFLAGS_OPT1 : identical to CXXFLAGS but with /OPTIMIZE=LEVEL=1.  This is a
DIFFERENT class from the NOOPT crashers: the file does NOT crash the back-end,
it just makes the LEVEL=3 GEM dataflow passes (CSE / strength-reduction) grind
pathologically - CXX$COMPILER sits CPU-bound in a tiny PC range with a static
working set and no new page faults, effectively forever (observed 78+ min of
emulated CPU on engines/kyra/screen.cpp, a 3780-line blitter full of tight
while(1) pointer-decode loops + two template<bool noXor> instantiations).  This
is especially costly under the es40 emulator.  LEVEL=1 keeps basic optimization
of those loops while skipping the expensive LEVEL=3 dataflow that stalls.  Such
files get an explicit rule below (see "Files needing /OPTIMIZE=LEVEL=1").

2026-08-28 - THE GRIND RATIONALE (reason (1)) IS DEAD ON REAL HARDWARE.  78+
minutes of EMULATED CPU is minutes of real CPU on an AlphaServer, so "the
optimizer is slow on this file" is no longer a reason to de-optimize it.
engines/kyra/screen.cpp was moved OFF this macro for exactly that reason - it is
the blitter and it now builds at full $(OPT).  What is LEFT on this macro is only
reason (2), files that CRASH a pass which runs only above LEVEL=1; those stay.
    CXXFLAGS_OPT1 = /name=(as_is,shortened)/float=ieee-
    /OPTIMIZE=(LEVEL=1,TUNE=HOST)/INCLUDE=$(INCS)/DEFINE=($(DEFS))-
    /WARNINGS=(DISABLE=(INTOVERFLOW,SHIFTCNTBIG,CODEUNREACHABLE,UNRECPRAGMA,-
    MISSINGRETURN))

CXXFLAGS_NODBG : identical to CXXFLAGS but with /NODEBUG/NOTRACEBACK.  This is a
FIFTH, distinct back-end crash class, and the only one that is NOT in a codegen
pass at all - the compiler dies GENERATING DEBUG INFORMATION:
  %SYSTEM-F-ACCVIO ... extend_memory_region <- allocate_hash_array <-
  create_hash_table <- initialize_me_debuggen <- me_generate_debug_information
  <- walk_variable <- walk_var_list <- walk_scope_variables <- walk_routine
i.e. it is walking a routine's scope VARIABLES to emit their debug symbols and
blows up while growing the debug-symbol hash table.  Two consequences:
  - /NOOPTIMIZE does NOT help.  This pass runs at EVERY optimization level; it is
    driven by me_driver, not by the optimizer.  Do not waste a round trip on it.
  - Reshaping the source does not help either (the reported crash is in symbol
    bookkeeping, not in the generated code), so re-running MMK never clears it.
We never ask for /DEBUG, but VMS CXX emits traceback DST records by default, which
is enough to run ME_DEBUGGEN.  /NODEBUG/NOTRACEBACK aims to skip the pass outright.
Cost: that ONE module has no symbolic traceback in a crash dump.  Nothing else
changes - the code generated is identical, and the linker does not care that one
module lacks DST records.

CORRECTION - /NODEBUG/NOTRACEBACK ALONE WAS NOT ENOUGH (observed: identical crash),
so this macro ALSO carries /NOOPTIMIZE.  An earlier version of this comment said
/NOOPTIMIZE "cannot help because the pass runs at every optimization level".  The
premise is true but the conclusion was wrong: the pass runs regardless, but WHAT IT
HAS TO WALK depends heavily on INLINING.  At /OPTIMIZE=LEVEL=3 the compiler inlines
every HashMap template member function into PEResources::parseResourceLevel, and
each inlined body contributes its OWN nested scope, locals and types to
walk_scope_variables - so the debug variable/type graph is multiplied by the
inlining.  /NOOPTIMIZE does no inlining at all, leaving a far smaller graph.  This
is the same mechanism as engines/kyra/screen.cpp, where the split helper routines
only stayed split (and only compiled) once /NOOPTIMIZE stopped them being
re-inlined into the oversized caller.
    CXXFLAGS_NODBG = /name=(as_is,shortened)/float=ieee-
    /NOOPTIMIZE/NODEBUG/NOTRACEBACK-
    /INCLUDE=$(INCS)/DEFINE=($(DEFS))-
    /WARNINGS=(DISABLE=(INTOVERFLOW,SHIFTCNTBIG,CODEUNREACHABLE,UNRECPRAGMA,-
    MISSINGRETURN))

CXXFLAGS_SIERRA : identical to CXXFLAGS but with STANDALONE_TARGET_SIERRA added.
Used for the SECOND compile of base/plugins.cpp only (-> plugins_sierra.obj).
It has to be a whole separate macro rather than "$(CXXFLAGS)/DEFINE=(...)":
a second /DEFINE on a CXX command line REPLACES the first, it does not merge,
so appending one would silently drop HAVE_CONFIG_H/SDL_BACKEND/ENABLE_* and
plugins.cpp would register NO engines at all - an image that links cleanly and
then reports "no game found" in every directory.
    CXXFLAGS_SIERRA = /name=(as_is,shortened)/float=ieee-
    $(OPT)/INCLUDE=$(INCS)-
    /DEFINE=($(DEFS),STANDALONE_TARGET_SIERRA)-
    /WARNINGS=(DISABLE=(INTOVERFLOW,SHIFTCNTBIG,CODEUNREACHABLE,UNRECPRAGMA,-
    MISSINGRETURN))

    LINKFLAGS =
For crash analysis:  LINKFLAGS = /MAP=KYRA.MAP/FULL

SDL 1.2 : same LIBSDL logical + X11 shareables as the FreeSCI loader.
  DEFINE LIBSDL SYS$SYSROOT:[SYSMGR.BUILD.SDL-1_2_14.SRC]
  DEFINE SDL    LIBSDL:[INCLUDE]        (or wherever the SDL headers live)

----------------------------------------------------------------------------
Object files (482) - grouped by top-level module
----------------------------------------------------------------------------
  shared  : AUDIO 57 + BACKENDS 37 + BASE 5 + COMMON 48 + ENGINES 6
            + GRAPHICS 33 + GUI 34 + IMAGE 20 + VIDEO 10   = 250
            (BASE is 5 because base/plugins.cpp is compiled TWICE)
  engines : KYRA 109 + SCI 82 + AGI 41                     = 232
482 objects from 481 .cpp files.
----------------------------------------------------------------------------

    AUDIO_OBJS = [.audio]adlib.obj,[.audio]audiostream.obj,[.audio.decoders]3do.obj,-
    [.audio.decoders]aac.obj,[.audio.decoders]adpcm.obj,[.audio.decoders]aiff.obj,-
    [.audio.decoders]flac.obj,[.audio.decoders]iff_sound.obj,[.audio.decoders]mac_snd.obj,-
    [.audio.decoders]mp3.obj,[.audio.decoders]qdm2.obj,[.audio.decoders]quicktime.obj,-
    [.audio.decoders]raw.obj,[.audio.decoders]voc.obj,[.audio.decoders]vorbis.obj,-
    [.audio.decoders]wave.obj,[.audio.decoders]xa.obj,[.audio]fmopl.obj,-
    [.audio]mididrv.obj,[.audio]midiparser.obj,[.audio]midiparser_qt.obj,-
    [.audio]midiparser_smf.obj,[.audio]midiparser_xmidi.obj,[.audio]midiplayer.obj,-
    [.audio]miles_adlib.obj,[.audio]miles_mt32.obj,[.audio]mixer.obj,-
    [.audio.mods]infogrames.obj,[.audio.mods]maxtrax.obj,[.audio.mods]module.obj,-
    [.audio.mods]paula.obj,[.audio.mods]protracker.obj,[.audio.mods]rjp1.obj,-
    [.audio.mods]soundfx.obj,[.audio.mods]tfmx.obj,[.audio]mpu401.obj,-
    [.audio]musicplugin.obj,[.audio]null.obj,[.audio]rate.obj,-
    [.audio.softsynth]appleiigs.obj,[.audio.softsynth]cms.obj,[.audio.softsynth]eas.obj,-
    [.audio.softsynth]fluidsynth.obj,[.audio.softsynth.fmtowns_pc98]towns_audio.obj,[.audio.softsynth.fmtowns_pc98]towns_euphony.obj,-
    [.audio.softsynth.fmtowns_pc98]towns_midi.obj,[.audio.softsynth.fmtowns_pc98]towns_pc98_driver.obj,[.audio.softsynth.fmtowns_pc98]towns_pc98_fmsynth.obj,-
    [.audio.softsynth.fmtowns_pc98]towns_pc98_plugins.obj,[.audio.softsynth]mt32.obj,[.audio.softsynth.opl]dbopl.obj,-
    [.audio.softsynth.opl]dosbox.obj,[.audio.softsynth.opl]mame.obj,[.audio.softsynth]pcspk.obj,-
    [.audio.softsynth]sid.obj,[.audio.softsynth]wave6581.obj,[.audio]timestamp.obj

    BACKENDS_OBJS = [.backends.audiocd.default]default-audiocd.obj,[.backends.audiocd.sdl]sdl-audiocd.obj,[.backends]base-backend.obj,-
    [.backends.events.default]default-events.obj,[.backends.events.sdl]sdl-events.obj,[.backends.fs]abstract-fs.obj,-
    [.backends.fs.chroot]chroot-fs.obj,[.backends.fs.chroot]chroot-fs-factory.obj,[.backends.fs.posix]posix-fs.obj,-
    [.backends.fs.posix]posix-fs-factory.obj,[.backends.fs]stdiostream.obj,[.backends.graphics.sdl]sdl-graphics.obj,-
    [.backends.graphics.surfacesdl]surfacesdl-graphics.obj,[.backends.log]log.obj,[.backends.midi]alsa.obj,-
    [.backends.midi]dmedia.obj,[.backends.midi]seq.obj,[.backends.midi]sndio.obj,-
    [.backends.midi]stmidi.obj,[.backends.midi]timidity.obj,[.backends.mixer.nullmixer]nullsdl-mixer.obj,-
    [.backends.mixer.sdl]sdl-mixer.obj,[.backends]modular-backend.obj,[.backends.mutex.sdl]sdl-mutex.obj,-
    [.backends.platform.sdl.posix]posix.obj,[.backends.platform.sdl.posix]posix-main.obj,[.backends.platform.sdl]sdl.obj,-
    [.backends.platform.sdl]sdl-window.obj,[.backends.plugins.posix]posix-provider.obj,[.backends.plugins.sdl]sdl-provider.obj,-
    [.backends.saves.default]default-saves.obj,[.backends.saves.posix]posix-saves.obj,[.backends.saves.recorder]recorder-saves.obj,-
    [.backends.saves]savefile.obj,[.backends.taskbar.unity]unity-taskbar.obj,[.backends.timer.default]default-timer.obj,-
    [.backends.timer.sdl]sdl-timer.obj

[.base]plugins_sierra.obj is the SECOND compile of base/plugins.cpp - see the
explicit rule near the end of this file.  Neither plugins object goes into
SCUMM_MAIN.OLB; each image gets its own on the LINK command line.
    BASE_OBJS = [.base]commandLine.obj,[.base]main.obj,[.base]plugins.obj,-
    [.base]plugins_sierra.obj,[.base]version.obj

    COMMON_OBJS = [.common]archive.obj,[.common]config-manager.obj,[.common]coroutines.obj,-
    [.common]cosinetables.obj,[.common]dcl.obj,[.common]dct.obj,-
    [.common]debug.obj,[.common]error.obj,[.common]EventDispatcher.obj,-
    [.common]EventMapper.obj,[.common]fft.obj,[.common]file.obj,-
    [.common]fs.obj,[.common]gui_options.obj,[.common]hashmap.obj,-
    [.common]huffman.obj,[.common]iff_container.obj,[.common]ini-file.obj,-
    [.common]installshield_cab.obj,[.common]language.obj,[.common]localization.obj,-
    [.common]macresman.obj,[.common]md5.obj,[.common]memorypool.obj,-
    [.common]mutex.obj,[.common]platform.obj,[.common]quicktime.obj,-
    [.common]random.obj,[.common]rational.obj,[.common]rdft.obj,-
    [.common]recorderfile.obj,[.common]rendermode.obj,[.common]sinetables.obj,-
    [.common]str.obj,[.common]stream.obj,[.common]system.obj,-
    [.common]textconsole.obj,[.common]tokenizer.obj,[.common]translation.obj,-
    [.common]unarj.obj,[.common]unzip.obj,[.common]ustr.obj,-
    [.common]util.obj,[.common]winexe.obj,[.common]winexe_ne.obj,-
    [.common]winexe_pe.obj,[.common]xmlparser.obj,[.common]zlib.obj

Generic engine-infrastructure objects, shared by BOTH images.
    ENGINES_OBJS = [.engines]advancedDetector.obj,[.engines]dialogs.obj,[.engines]engine.obj,-
    [.engines]game.obj,[.engines]obsolete.obj,[.engines]savestate.obj

Per-engine object groups.  Each one becomes its OWN object library (see the
"WHY TWO EXECUTABLES AND FOUR OBJECT LIBRARIES" note at the top of this file):
the union of these three has 22 colliding basenames, and an OpenVMS object
library keys modules by basename alone.
    KYRA_ENG_OBJS = [.engines.kyra]animator_hof.obj,[.engines.kyra]animator_lok.obj,[.engines.kyra]animator_mr.obj,-
    [.engines.kyra]animator_tim.obj,[.engines.kyra]animator_v2.obj,[.engines.kyra]chargen.obj,-
    [.engines.kyra]darkmoon.obj,[.engines.kyra]debugger.obj,[.engines.kyra]detection.obj,-
    [.engines.kyra]eob.obj,[.engines.kyra]eobcommon.obj,[.engines.kyra]gui.obj,-
    [.engines.kyra]gui_eob.obj,[.engines.kyra]gui_hof.obj,[.engines.kyra]gui_lok.obj,-
    [.engines.kyra]gui_lol.obj,[.engines.kyra]gui_mr.obj,[.engines.kyra]gui_rpg.obj,-
    [.engines.kyra]gui_v1.obj,[.engines.kyra]gui_v2.obj,[.engines.kyra]items_eob.obj,-
    [.engines.kyra]items_hof.obj,[.engines.kyra]items_lok.obj,[.engines.kyra]items_lol.obj,-
    [.engines.kyra]items_mr.obj,[.engines.kyra]items_v2.obj,[.engines.kyra]kyra_hof.obj,-
    [.engines.kyra]kyra_lok.obj,[.engines.kyra]kyra_mr.obj,[.engines.kyra]kyra_rpg.obj,-
    [.engines.kyra]kyra_v1.obj,[.engines.kyra]kyra_v2.obj,[.engines.kyra]lol.obj,-
    [.engines.kyra]magic_eob.obj,[.engines.kyra]resource.obj,[.engines.kyra]resource_intern.obj,-
    [.engines.kyra]saveload.obj,[.engines.kyra]saveload_eob.obj,[.engines.kyra]saveload_hof.obj,-
    [.engines.kyra]saveload_lok.obj,[.engines.kyra]saveload_lol.obj,[.engines.kyra]saveload_mr.obj,-
    [.engines.kyra]saveload_rpg.obj,[.engines.kyra]scene_eob.obj,[.engines.kyra]scene_hof.obj,-
    [.engines.kyra]scene_lok.obj,[.engines.kyra]scene_lol.obj,[.engines.kyra]scene_mr.obj,-
    [.engines.kyra]scene_rpg.obj,[.engines.kyra]scene_v1.obj,[.engines.kyra]scene_v2.obj,-
    [.engines.kyra]screen.obj,[.engines.kyra]screen_delta.obj,[.engines.kyra]screen_eob.obj,-
    [.engines.kyra]screen_hof.obj,[.engines.kyra]screen_lok.obj,[.engines.kyra]screen_lol.obj,-
    [.engines.kyra]screen_mr.obj,[.engines.kyra]screen_v2.obj,[.engines.kyra]script.obj,-
    [.engines.kyra]script_eob.obj,[.engines.kyra]script_hof.obj,[.engines.kyra]script_lok.obj,-
    [.engines.kyra]script_lol.obj,[.engines.kyra]script_mr.obj,[.engines.kyra]script_tim.obj,-
    [.engines.kyra]script_v1.obj,[.engines.kyra]script_v2.obj,[.engines.kyra]seqplayer.obj,-
    [.engines.kyra]sequences_darkmoon.obj,[.engines.kyra]sequences_eob.obj,[.engines.kyra]sequences_hof.obj,-
    [.engines.kyra]sequences_lok.obj,[.engines.kyra]sequences_lol.obj,[.engines.kyra]sequences_mr.obj,-
    [.engines.kyra]sequences_v2.obj,[.engines.kyra]sound.obj,[.engines.kyra]sound_adlib.obj,-
    [.engines.kyra]sound_amiga.obj,[.engines.kyra]sound_digital.obj,[.engines.kyra]sound_lok.obj,-
    [.engines.kyra]sound_lol.obj,[.engines.kyra]sound_midi.obj,[.engines.kyra]sound_pcspk.obj,-
    [.engines.kyra]sound_towns.obj,[.engines.kyra]sprites.obj,[.engines.kyra]sprites_eob.obj,-
    [.engines.kyra]sprites_lol.obj,[.engines.kyra]sprites_rpg.obj,[.engines.kyra]staticres.obj,-
    [.engines.kyra]staticres_eob.obj,[.engines.kyra]staticres_lol.obj,[.engines.kyra]staticres_rpg.obj,-
    [.engines.kyra]text.obj,[.engines.kyra]text_hof.obj,[.engines.kyra]text_lok.obj,-
    [.engines.kyra]text_lol.obj,[.engines.kyra]text_mr.obj,[.engines.kyra]text_rpg.obj,-
    [.engines.kyra]timer.obj,[.engines.kyra]timer_eob.obj,[.engines.kyra]timer_hof.obj,-
    [.engines.kyra]timer_lok.obj,[.engines.kyra]timer_lol.obj,[.engines.kyra]timer_mr.obj,-
    [.engines.kyra]timer_rpg.obj,[.engines.kyra]util.obj,[.engines.kyra]vqa.obj,-
    [.engines.kyra]wsamovie.obj

SCI 0/1/1.1 + SCI32.  All 82 TUs are compiled unconditionally; the SCI32-only
ones (engine/kgraphics32, graphics/controls32, graphics/frameout,
graphics/paint32, graphics/palette32, graphics/text32, video/robot_decoder)
self-guard on #ifdef ENABLE_SCI32 and would compile to EMPTY objects without
it - which is exactly what an empty .OBJ means here, not a failed compile.
    SCI_ENG_OBJS = [.engines.sci]console.obj,[.engines.sci]decompressor.obj,[.engines.sci]detection.obj,-
    [.engines.sci]event.obj,[.engines.sci]resource.obj,[.engines.sci]resource_audio.obj,-
    [.engines.sci]sci.obj,[.engines.sci]util.obj,[.engines.sci.engine]features.obj,-
    [.engines.sci.engine]file.obj,[.engines.sci.engine]gc.obj,[.engines.sci.engine]kernel.obj,-
    [.engines.sci.engine]kevent.obj,[.engines.sci.engine]kfile.obj,[.engines.sci.engine]kgraphics.obj,-
    [.engines.sci.engine]kgraphics32.obj,[.engines.sci.engine]klists.obj,[.engines.sci.engine]kmath.obj,-
    [.engines.sci.engine]kmenu.obj,[.engines.sci.engine]kmisc.obj,[.engines.sci.engine]kmovement.obj,-
    [.engines.sci.engine]kparse.obj,[.engines.sci.engine]kpathing.obj,[.engines.sci.engine]kscripts.obj,-
    [.engines.sci.engine]ksound.obj,[.engines.sci.engine]kstring.obj,[.engines.sci.engine]kvideo.obj,-
    [.engines.sci.engine]message.obj,[.engines.sci.engine]object.obj,[.engines.sci.engine]savegame.obj,-
    [.engines.sci.engine]script.obj,[.engines.sci.engine]script_patches.obj,[.engines.sci.engine]scriptdebug.obj,-
    [.engines.sci.engine]seg_manager.obj,[.engines.sci.engine]segment.obj,[.engines.sci.engine]selector.obj,-
    [.engines.sci.engine]state.obj,[.engines.sci.engine]static_selectors.obj,[.engines.sci.engine]vm.obj,-
    [.engines.sci.engine]vm_types.obj,[.engines.sci.engine]workarounds.obj,[.engines.sci.graphics]animate.obj,-
    [.engines.sci.graphics]cache.obj,[.engines.sci.graphics]compare.obj,[.engines.sci.graphics]controls16.obj,-
    [.engines.sci.graphics]controls32.obj,[.engines.sci.graphics]coordadjuster.obj,[.engines.sci.graphics]cursor.obj,-
    [.engines.sci.graphics]font.obj,[.engines.sci.graphics]fontsjis.obj,[.engines.sci.graphics]frameout.obj,-
    [.engines.sci.graphics]maciconbar.obj,[.engines.sci.graphics]menu.obj,[.engines.sci.graphics]paint.obj,-
    [.engines.sci.graphics]paint16.obj,[.engines.sci.graphics]paint32.obj,[.engines.sci.graphics]palette.obj,-
    [.engines.sci.graphics]palette32.obj,[.engines.sci.graphics]picture.obj,[.engines.sci.graphics]portrait.obj,-
    [.engines.sci.graphics]ports.obj,[.engines.sci.graphics]screen.obj,[.engines.sci.graphics]text16.obj,-
    [.engines.sci.graphics]text32.obj,[.engines.sci.graphics]transitions.obj,[.engines.sci.graphics]view.obj,-
    [.engines.sci.parser]grammar.obj,[.engines.sci.parser]said.obj,[.engines.sci.parser]vocabulary.obj,-
    [.engines.sci.sound]audio.obj,[.engines.sci.sound]midiparser_sci.obj,[.engines.sci.sound]music.obj,-
    [.engines.sci.sound]soundcmd.obj,[.engines.sci.sound.drivers]adlib.obj,[.engines.sci.sound.drivers]amigamac.obj,-
    [.engines.sci.sound.drivers]cms.obj,[.engines.sci.sound.drivers]fb01.obj,[.engines.sci.sound.drivers]fmtowns.obj,-
    [.engines.sci.sound.drivers]midi.obj,[.engines.sci.sound.drivers]pcjr.obj,[.engines.sci.video]robot_decoder.obj,-
    [.engines.sci.video]seq_decoder.obj

AGI v1/v2/v3 plus preAGI (Mickey, Troll, Winnie).
    AGI_ENG_OBJS = [.engines.agi]agi.obj,[.engines.agi]checks.obj,[.engines.agi]console.obj,-
    [.engines.agi]cycle.obj,[.engines.agi]detection.obj,[.engines.agi]font.obj,-
    [.engines.agi]global.obj,[.engines.agi]graphics.obj,[.engines.agi]id.obj,-
    [.engines.agi]inv.obj,[.engines.agi]keyboard.obj,[.engines.agi]loader_v1.obj,-
    [.engines.agi]loader_v2.obj,[.engines.agi]loader_v3.obj,[.engines.agi]logic.obj,-
    [.engines.agi]lzw.obj,[.engines.agi]menu.obj,[.engines.agi]motion.obj,-
    [.engines.agi]objects.obj,[.engines.agi]op_cmd.obj,[.engines.agi]op_dbg.obj,-
    [.engines.agi]op_test.obj,[.engines.agi]opcodes.obj,[.engines.agi]picture.obj,-
    [.engines.agi]preagi.obj,[.engines.agi]preagi_mickey.obj,[.engines.agi]preagi_troll.obj,-
    [.engines.agi]preagi_winnie.obj,[.engines.agi]saveload.obj,[.engines.agi]sound.obj,-
    [.engines.agi]sound_2gs.obj,[.engines.agi]sound_coco3.obj,[.engines.agi]sound_midi.obj,-
    [.engines.agi]sound_pcjr.obj,[.engines.agi]sound_sarien.obj,[.engines.agi]sprite.obj,-
    [.engines.agi]systemui.obj,[.engines.agi]text.obj,[.engines.agi]view.obj,-
    [.engines.agi]wagparser.obj,[.engines.agi]words.obj

    GRAPHICS_OBJS = [.graphics]conversion.obj,[.graphics]cursorman.obj,[.graphics]font.obj,-
    [.graphics]fontman.obj,[.graphics.fonts]bdf.obj,[.graphics.fonts]consolefont.obj,-
    [.graphics.fonts]newfont_big.obj,[.graphics.fonts]newfont.obj,[.graphics.fonts]ttf.obj,-
    [.graphics.fonts]winfont.obj,[.graphics]maccursor.obj,[.graphics]pixelformat.obj,-
    [.graphics]primitives.obj,[.graphics.scaler]2xsai.obj,[.graphics.scaler]aspect.obj,-
    [.graphics]scaler.obj,[.graphics.scaler]downscaler.obj,[.graphics.scaler]hq2x.obj,-
    [.graphics.scaler]hq3x.obj,[.graphics.scaler]scale2x.obj,[.graphics.scaler]scale3x.obj,-
    [.graphics.scaler]scalebit.obj,[.graphics.scaler]thumbnail_intern.obj,[.graphics]sjis.obj,-
    [.graphics]surface.obj,[.graphics]thumbnail.obj,[.graphics]transform_struct.obj,-
    [.graphics]transform_tools.obj,[.graphics]transparent_surface.obj,[.graphics]VectorRenderer.obj,-
    [.graphics]VectorRendererSpec.obj,[.graphics]wincursor.obj,[.graphics]yuv_to_rgb.obj

    GUI_OBJS = [.gui]about.obj,[.gui]browser.obj,[.gui]chooser.obj,-
    [.gui]console.obj,[.gui]debugger.obj,[.gui]dialog.obj,-
    [.gui]editrecorddialog.obj,[.gui]error.obj,[.gui]EventRecorder.obj,-
    [.gui]filebrowser-dialog.obj,[.gui]gui-manager.obj,[.gui]launcher.obj,-
    [.gui]massadd.obj,[.gui]message.obj,[.gui]object.obj,-
    [.gui]onscreendialog.obj,[.gui]options.obj,[.gui]predictivedialog.obj,-
    [.gui]recorderdialog.obj,[.gui]saveload.obj,[.gui]saveload-dialog.obj,-
    [.gui]themebrowser.obj,[.gui]ThemeEngine.obj,[.gui]ThemeEval.obj,-
    [.gui]ThemeLayout.obj,[.gui]ThemeParser.obj,[.gui]Tooltip.obj,-
    [.gui]widget.obj,[.gui.widgets]editable.obj,[.gui.widgets]edittext.obj,-
    [.gui.widgets]list.obj,[.gui.widgets]popup.obj,[.gui.widgets]scrollbar.obj,-
    [.gui.widgets]tab.obj

    IMAGE_OBJS = [.image]bmp.obj,[.image.codecs]bmp_raw.obj,[.image.codecs]cdtoons.obj,-
    [.image.codecs]cinepak.obj,[.image.codecs]codec.obj,[.image.codecs]indeo3.obj,-
    [.image.codecs]mjpeg.obj,[.image.codecs]msrle.obj,[.image.codecs]msvideo1.obj,-
    [.image.codecs]qtrle.obj,[.image.codecs]rpza.obj,[.image.codecs]smc.obj,-
    [.image.codecs]svq1.obj,[.image.codecs]truemotion1.obj,[.image]iff.obj,-
    [.image]jpeg.obj,[.image]pcx.obj,[.image]pict.obj,-
    [.image]png.obj,[.image]tga.obj

    VIDEO_OBJS = [.video]avi_decoder.obj,[.video]bink_decoder.obj,[.video]coktel_decoder.obj,-
    [.video]dxa_decoder.obj,[.video]flic_decoder.obj,[.video]mpegps_decoder.obj,-
    [.video]psx_decoder.obj,[.video]qt_decoder.obj,[.video]smk_decoder.obj,-
    [.video]video_decoder.obj

MAIN_OBJS : everything that is NOT engine-specific.  These 250 objects go into
SCUMM_MAIN.OLB (minus the 5 that are handled on the LINK command line, see the
chunk section) and are shared byte-for-byte between KYRA.EXE and SIERRA.EXE.
    MAIN_OBJS = $(AUDIO_OBJS),-
    $(BACKENDS_OBJS),-
    $(BASE_OBJS),-
    $(COMMON_OBJS),-
    $(ENGINES_OBJS),-
    $(GRAPHICS_OBJS),-
    $(GUI_OBJS),-
    $(IMAGE_OBJS),-
    $(VIDEO_OBJS)

ALL_OBJS is used only by the `objects` convenience target; the real dependency
chains go through the four object libraries.
    ALL_OBJS = $(MAIN_OBJS),-
    $(KYRA_ENG_OBJS),-
    $(SCI_ENG_OBJS),-
    $(AGI_ENG_OBJS)

----------------------------------------------------------------------------
Object-list chunks for the four object libraries.
MMK/MMS caps a single expanded action-line element at ~1024 chars, which is
SMALLER than DCL's own limit, so even one `@ write objfile "$(AUDIO_OBJS)"`
overflows.  These <=700-char chunks are each passed to one LIBRARY/INSERT
command (~35 chars of command text + <=691 chars of list = well under the cap).
Chunks are split on group boundaries and verified to reproduce $(ALL_OBJS)
exactly.  Keep in sync if adding files.

  O_AUDIO_* O_BACKENDS_* O_BASE_1 O_COMMON_* O_ENGINES_1 O_GRAPHICS_*
  O_GUI_* O_IMAGE_1 O_VIDEO_1        -> SCUMM_MAIN.OLB  (245 modules)
  O_KYRA_1..5                        -> KYRA_ENG.OLB    (109 modules)
  O_SCI_1..5                         -> SCI_ENG.OLB     ( 82 modules)
  O_AGI_1..2                         -> AGI_ENG.OLB     ( 41 modules)

245 modules in the SCUMM_MAIN chunks, not 250.  FIVE objects are deliberately
absent and are linked directly on the LINK command line instead (see the
kyra.exe / sierra.exe rules):
  posix-main.obj                       - carries the transfer address
  [.base]plugins.obj                   - PER-IMAGE: KYRA.EXE only
  [.base]plugins_sierra.obj            - PER-IMAGE: SIERRA.EXE only
  [.gui]error.obj, [.common]quicktime.obj  - DUPLICATE MODULE NAMES
An OpenVMS object library keys modules by the object's FILE NAME, ignoring the
directory, so two objects with the same basename collide: LIBRARY/INSERT keeps
only one and the other is silently unavailable to the linker.  Within
SCUMM_MAIN there are exactly TWO such pairs (common+gui error, audio+common
quicktime), which caused %LINK-W-NUDFSYMS for GUI::displayErrorDialog and every
Common::QuickTimeParser method.  Listing one member of each pair on the command
line keeps both reachable.  Do NOT re-add them here.  If you add a file whose
basename already exists elsewhere in SCUMM_MAIN, give it the same treatment.

THREE FORMER COMMAND-LINE OBJECTS WERE MOVED BACK INTO A LIBRARY:
[.engines.kyra]debugger.obj, saveload.obj and util.obj used to sit on the LINK
command line because they collided with [.gui]debugger, [.gui]saveload and
[.common]util in the single old KYRA_OBJS.OLB.  They now live in KYRA_ENG.OLB,
a DIFFERENT library from their namesakes, so the collision is gone and the
command line no longer needs them.  Note the consequence: they are now
extracted ON DEMAND rather than always included.  That is correct - plugins.obj
holds a hard reference to g_KYRA_getObject, which force-extracts
[.engines.kyra]detection.obj and pulls the rest of the engine in behind it.

BASENAME COLLISIONS THAT ARE NOW HARMLESS, for the record: sci and agi share
console, detection, font, menu, picture and view; kyra/sci/agi collectively add
adlib, cms, file, message, object, resource, saveload, screen, script, sound,
sound_midi, text and util against the shared tree.  ALL of these are safe ONLY
because of the library split.  If you ever collapse the four libraries back into
one, ~22 modules will vanish silently.

HISTORICAL NOTE - ThemeEval.obj / scale2x.obj were once ALSO listed on the LINK
command line, under the theory that the library "failed to deliver" them: their
modules never appeared in LIBRARY/LIST/NAMES and every symbol they define came
back %LINK-W-NUDFSYMS.  That theory was WRONG and the workaround was treating a
symptom.  Moving them to the command line made the linker read them directly and
it then reported the real cause:
    %LINK-W-EMPTYFILE, no modules found in file ...[.GUI]THEMEEVAL.OBJ;1
    %LINK-W-EMPTYFILE, no modules found in file ...[.GRAPHICS.SCALER]SCALE2X.OBJ;1
The two .OBJ files were EMPTY - zero modules - so there was nothing for LIBRARY
to insert and nothing for LINK to resolve.  The tell-tale was that the undefined
set was EXACTLY the complete export list of each source file (all 9 ThemeEval
methods + ~ThemeEval, and all 3 scale2x_*_def; the _mmx variants are __GNUC__-
guarded), not a subset - "no code emitted at all", not a miscompile.  Both are
back in the chunks below.  If you ever see this again, DO NOT reach for the
command line: DIRECTORY/SIZE the .OBJ first.  A 0-block object means the compile
never produced code and the fix is to rebuild that file, not to relink it.
    O_AUDIO_1 = [.audio]adlib.obj,[.audio]audiostream.obj,[.audio.decoders]3do.obj,[.audio.decoders]aac.obj,[.audio.decoders]adpcm.obj,[.audio.decoders]aiff.obj,[.audio.decoders]flac.obj,[.audio.decoders]iff_sound.obj,[.audio.decoders]mac_snd.obj,[.audio.decoders]mp3.obj,[.audio.decoders]qdm2.obj,[.audio.decoders]quicktime.obj,[.audio.decoders]raw.obj,[.audio.decoders]voc.obj,[.audio.decoders]vorbis.obj,[.audio.decoders]wave.obj,[.audio.decoders]xa.obj,[.audio]fmopl.obj,[.audio]mididrv.obj,[.audio]midiparser.obj,[.audio]midiparser_qt.obj,[.audio]midiparser_smf.obj,[.audio]midiparser_xmidi.obj,[.audio]midiplayer.obj,[.audio]miles_adlib.obj,[.audio]miles_mt32.obj,[.audio]mixer.obj,[.audio.mods]infogrames.obj
    O_AUDIO_2 = [.audio.mods]maxtrax.obj,[.audio.mods]module.obj,[.audio.mods]paula.obj,[.audio.mods]protracker.obj,[.audio.mods]rjp1.obj,[.audio.mods]soundfx.obj,[.audio.mods]tfmx.obj,[.audio]mpu401.obj,[.audio]musicplugin.obj,[.audio]null.obj,[.audio]rate.obj,[.audio.softsynth]appleiigs.obj,[.audio.softsynth]cms.obj,[.audio.softsynth]eas.obj,[.audio.softsynth]fluidsynth.obj,[.audio.softsynth.fmtowns_pc98]towns_audio.obj,[.audio.softsynth.fmtowns_pc98]towns_euphony.obj,[.audio.softsynth.fmtowns_pc98]towns_midi.obj,[.audio.softsynth.fmtowns_pc98]towns_pc98_driver.obj,[.audio.softsynth.fmtowns_pc98]towns_pc98_fmsynth.obj,[.audio.softsynth.fmtowns_pc98]towns_pc98_plugins.obj,[.audio.softsynth]mt32.obj
    O_AUDIO_3 = [.audio.softsynth.opl]dbopl.obj,[.audio.softsynth.opl]dosbox.obj,[.audio.softsynth.opl]mame.obj,[.audio.softsynth]pcspk.obj,[.audio.softsynth]sid.obj,[.audio.softsynth]wave6581.obj,[.audio]timestamp.obj
    O_BACKENDS_1 = [.backends.audiocd.default]default-audiocd.obj,[.backends.audiocd.sdl]sdl-audiocd.obj,[.backends]base-backend.obj,[.backends.events.default]default-events.obj,[.backends.events.sdl]sdl-events.obj,[.backends.fs]abstract-fs.obj,[.backends.fs.chroot]chroot-fs.obj,[.backends.fs.chroot]chroot-fs-factory.obj,[.backends.fs.posix]posix-fs.obj,[.backends.fs.posix]posix-fs-factory.obj,[.backends.fs]stdiostream.obj,[.backends.graphics.sdl]sdl-graphics.obj,[.backends.graphics.surfacesdl]surfacesdl-graphics.obj,[.backends.log]log.obj,[.backends.midi]alsa.obj,[.backends.midi]dmedia.obj,[.backends.midi]seq.obj,[.backends.midi]sndio.obj,[.backends.midi]stmidi.obj,[.backends.midi]timidity.obj
NOTE: [.backends.platform.sdl.posix]posix-main.obj is DELIBERATELY omitted from
this chunk - it carries the program transfer address (main) and is linked
directly on the LINK command line instead (see the kyra.exe rule).  The OpenVMS
linker does not reliably promote an object's transfer address to the image
transfer address when that object is pulled in via an /OPTIONS file, which is
what caused %LINK-W-USRTFR.  Keeping it on the command line (like the working
dosbox port does for all its objects) fixes that.  Do NOT also list it here, or
`main' becomes multiply defined.
    O_BACKENDS_2 = [.backends.mixer.nullmixer]nullsdl-mixer.obj,[.backends.mixer.sdl]sdl-mixer.obj,[.backends]modular-backend.obj,[.backends.mutex.sdl]sdl-mutex.obj,[.backends.platform.sdl.posix]posix.obj,[.backends.platform.sdl]sdl.obj,[.backends.platform.sdl]sdl-window.obj,[.backends.plugins.posix]posix-provider.obj,[.backends.plugins.sdl]sdl-provider.obj,[.backends.saves.default]default-saves.obj,[.backends.saves.posix]posix-saves.obj,[.backends.saves.recorder]recorder-saves.obj,[.backends.saves]savefile.obj,[.backends.taskbar.unity]unity-taskbar.obj,[.backends.timer.default]default-timer.obj,[.backends.timer.sdl]sdl-timer.obj
    O_BASE_1 = [.base]commandLine.obj,[.base]main.obj,[.base]version.obj
    O_COMMON_1 = [.common]archive.obj,[.common]config-manager.obj,[.common]coroutines.obj,[.common]cosinetables.obj,[.common]dcl.obj,[.common]dct.obj,[.common]debug.obj,[.common]error.obj,[.common]EventDispatcher.obj,[.common]EventMapper.obj,[.common]fft.obj,[.common]file.obj,[.common]fs.obj,[.common]gui_options.obj,[.common]hashmap.obj,[.common]huffman.obj,[.common]iff_container.obj,[.common]ini-file.obj,[.common]installshield_cab.obj,[.common]language.obj,[.common]localization.obj,[.common]macresman.obj,[.common]md5.obj,[.common]memorypool.obj,[.common]mutex.obj,[.common]platform.obj,[.common]random.obj,[.common]rational.obj,[.common]rdft.obj,[.common]recorderfile.obj
    O_COMMON_2 = [.common]rendermode.obj,[.common]sinetables.obj,[.common]str.obj,[.common]stream.obj,[.common]system.obj,[.common]textconsole.obj,[.common]tokenizer.obj,[.common]translation.obj,[.common]unarj.obj,[.common]unzip.obj,[.common]ustr.obj,[.common]util.obj,[.common]winexe.obj,[.common]winexe_ne.obj,[.common]winexe_pe.obj,[.common]xmlparser.obj,[.common]zlib.obj
    O_ENGINES_1 = [.engines]advancedDetector.obj,[.engines]dialogs.obj,[.engines]engine.obj,[.engines]game.obj,[.engines]obsolete.obj,[.engines]savestate.obj
    O_GRAPHICS_1 = [.graphics]conversion.obj,[.graphics]cursorman.obj,[.graphics]font.obj,[.graphics]fontman.obj,[.graphics.fonts]bdf.obj,[.graphics.fonts]consolefont.obj,[.graphics.fonts]newfont_big.obj,[.graphics.fonts]newfont.obj,[.graphics.fonts]ttf.obj,[.graphics.fonts]winfont.obj,[.graphics]maccursor.obj,[.graphics]pixelformat.obj,[.graphics]primitives.obj,[.graphics.scaler]2xsai.obj,[.graphics.scaler]aspect.obj,[.graphics]scaler.obj,[.graphics.scaler]downscaler.obj,[.graphics.scaler]hq2x.obj,[.graphics.scaler]hq3x.obj,[.graphics.scaler]scale2x.obj,[.graphics.scaler]scale3x.obj,[.graphics.scaler]scalebit.obj,[.graphics.scaler]thumbnail_intern.obj,[.graphics]sjis.obj,[.graphics]surface.obj
    O_GRAPHICS_2 = [.graphics]thumbnail.obj,[.graphics]transform_struct.obj,[.graphics]transform_tools.obj,[.graphics]transparent_surface.obj,[.graphics]VectorRenderer.obj,[.graphics]VectorRendererSpec.obj,[.graphics]wincursor.obj,[.graphics]yuv_to_rgb.obj
    O_GUI_1 = [.gui]about.obj,[.gui]browser.obj,[.gui]chooser.obj,[.gui]console.obj,[.gui]dialog.obj,[.gui]editrecorddialog.obj,[.gui]EventRecorder.obj,[.gui]filebrowser-dialog.obj,[.gui]gui-manager.obj,[.gui]launcher.obj,[.gui]massadd.obj,[.gui]message.obj,[.gui]object.obj,[.gui]onscreendialog.obj,[.gui]options.obj,[.gui]predictivedialog.obj,[.gui]recorderdialog.obj,[.gui]saveload-dialog.obj,[.gui]themebrowser.obj,[.gui]ThemeEngine.obj,[.gui]ThemeEval.obj,[.gui]ThemeLayout.obj,[.gui]ThemeParser.obj,[.gui]Tooltip.obj,[.gui]widget.obj,[.gui.widgets]editable.obj,[.gui.widgets]edittext.obj,[.gui.widgets]list.obj,[.gui.widgets]popup.obj
[.gui]debugger.obj and [.gui]saveload.obj are HERE now (they used to be on the
LINK command line).  Their kyra namesakes moved to KYRA_ENG.OLB, so nothing in
SCUMM_MAIN collides with them any more.  [.gui]error.obj is still absent - it
collides with [.common]error.obj, which IS in this library.
    O_GUI_2 = [.gui]debugger.obj,[.gui]saveload.obj,[.gui.widgets]scrollbar.obj,[.gui.widgets]tab.obj
    O_IMAGE_1 = [.image]bmp.obj,[.image.codecs]bmp_raw.obj,[.image.codecs]cdtoons.obj,[.image.codecs]cinepak.obj,[.image.codecs]codec.obj,[.image.codecs]indeo3.obj,[.image.codecs]mjpeg.obj,[.image.codecs]msrle.obj,[.image.codecs]msvideo1.obj,[.image.codecs]qtrle.obj,[.image.codecs]rpza.obj,[.image.codecs]smc.obj,[.image.codecs]svq1.obj,[.image.codecs]truemotion1.obj,[.image]iff.obj,[.image]jpeg.obj,[.image]pcx.obj,[.image]pict.obj,[.image]png.obj,[.image]tga.obj
    O_VIDEO_1 = [.video]avi_decoder.obj,[.video]bink_decoder.obj,[.video]coktel_decoder.obj,[.video]dxa_decoder.obj,[.video]flic_decoder.obj,[.video]mpegps_decoder.obj,[.video]psx_decoder.obj,[.video]qt_decoder.obj,[.video]smk_decoder.obj,[.video]video_decoder.obj

--- KYRA_ENG.OLB chunks (109 modules, all of [.engines.kyra]) ---
    O_KYRA_1 = [.engines.kyra]animator_hof.obj,[.engines.kyra]animator_lok.obj,[.engines.kyra]animator_mr.obj,[.engines.kyra]animator_tim.obj,[.engines.kyra]animator_v2.obj,[.engines.kyra]chargen.obj,[.engines.kyra]darkmoon.obj,[.engines.kyra]debugger.obj,[.engines.kyra]detection.obj,[.engines.kyra]eob.obj,[.engines.kyra]eobcommon.obj,[.engines.kyra]gui.obj,[.engines.kyra]gui_eob.obj,[.engines.kyra]gui_hof.obj,[.engines.kyra]gui_lok.obj,[.engines.kyra]gui_lol.obj,[.engines.kyra]gui_mr.obj,[.engines.kyra]gui_rpg.obj,[.engines.kyra]gui_v1.obj,[.engines.kyra]gui_v2.obj,[.engines.kyra]items_eob.obj,[.engines.kyra]items_hof.obj,[.engines.kyra]items_lok.obj,[.engines.kyra]items_lol.obj
    O_KYRA_2 = [.engines.kyra]items_mr.obj,[.engines.kyra]items_v2.obj,[.engines.kyra]kyra_hof.obj,[.engines.kyra]kyra_lok.obj,[.engines.kyra]kyra_mr.obj,[.engines.kyra]kyra_rpg.obj,[.engines.kyra]kyra_v1.obj,[.engines.kyra]kyra_v2.obj,[.engines.kyra]lol.obj,[.engines.kyra]magic_eob.obj,[.engines.kyra]resource.obj,[.engines.kyra]resource_intern.obj,[.engines.kyra]saveload.obj,[.engines.kyra]saveload_eob.obj,[.engines.kyra]saveload_hof.obj,[.engines.kyra]saveload_lok.obj,[.engines.kyra]saveload_lol.obj,[.engines.kyra]saveload_mr.obj,[.engines.kyra]saveload_rpg.obj,[.engines.kyra]scene_eob.obj,[.engines.kyra]scene_hof.obj,[.engines.kyra]scene_lok.obj,[.engines.kyra]scene_lol.obj
    O_KYRA_3 = [.engines.kyra]scene_mr.obj,[.engines.kyra]scene_rpg.obj,[.engines.kyra]scene_v1.obj,[.engines.kyra]scene_v2.obj,[.engines.kyra]screen.obj,[.engines.kyra]screen_delta.obj,[.engines.kyra]screen_eob.obj,[.engines.kyra]screen_hof.obj,[.engines.kyra]screen_lok.obj,[.engines.kyra]screen_lol.obj,[.engines.kyra]screen_mr.obj,[.engines.kyra]screen_v2.obj,[.engines.kyra]script.obj,[.engines.kyra]script_eob.obj,[.engines.kyra]script_hof.obj,[.engines.kyra]script_lok.obj,[.engines.kyra]script_lol.obj,[.engines.kyra]script_mr.obj,[.engines.kyra]script_tim.obj,[.engines.kyra]script_v1.obj,[.engines.kyra]script_v2.obj,[.engines.kyra]seqplayer.obj,[.engines.kyra]sequences_darkmoon.obj
    O_KYRA_4 = [.engines.kyra]sequences_eob.obj,[.engines.kyra]sequences_hof.obj,[.engines.kyra]sequences_lok.obj,[.engines.kyra]sequences_lol.obj,[.engines.kyra]sequences_mr.obj,[.engines.kyra]sequences_v2.obj,[.engines.kyra]sound.obj,[.engines.kyra]sound_adlib.obj,[.engines.kyra]sound_amiga.obj,[.engines.kyra]sound_digital.obj,[.engines.kyra]sound_lok.obj,[.engines.kyra]sound_lol.obj,[.engines.kyra]sound_midi.obj,[.engines.kyra]sound_pcspk.obj,[.engines.kyra]sound_towns.obj,[.engines.kyra]sprites.obj,[.engines.kyra]sprites_eob.obj,[.engines.kyra]sprites_lol.obj,[.engines.kyra]sprites_rpg.obj,[.engines.kyra]staticres.obj,[.engines.kyra]staticres_eob.obj,[.engines.kyra]staticres_lol.obj
    O_KYRA_5 = [.engines.kyra]staticres_rpg.obj,[.engines.kyra]text.obj,[.engines.kyra]text_hof.obj,[.engines.kyra]text_lok.obj,[.engines.kyra]text_lol.obj,[.engines.kyra]text_mr.obj,[.engines.kyra]text_rpg.obj,[.engines.kyra]timer.obj,[.engines.kyra]timer_eob.obj,[.engines.kyra]timer_hof.obj,[.engines.kyra]timer_lok.obj,[.engines.kyra]timer_lol.obj,[.engines.kyra]timer_mr.obj,[.engines.kyra]timer_rpg.obj,[.engines.kyra]util.obj,[.engines.kyra]vqa.obj,[.engines.kyra]wsamovie.obj

--- SCI_ENG.OLB chunks (82 modules, [.engines.sci] and its 6 subdirs) ---
    O_SCI_1 = [.engines.sci]console.obj,[.engines.sci]decompressor.obj,[.engines.sci]detection.obj,[.engines.sci]event.obj,[.engines.sci]resource.obj,[.engines.sci]resource_audio.obj,[.engines.sci]sci.obj,[.engines.sci]util.obj,[.engines.sci.engine]features.obj,[.engines.sci.engine]file.obj,[.engines.sci.engine]gc.obj,[.engines.sci.engine]kernel.obj,[.engines.sci.engine]kevent.obj,[.engines.sci.engine]kfile.obj,[.engines.sci.engine]kgraphics.obj,[.engines.sci.engine]kgraphics32.obj,[.engines.sci.engine]klists.obj,[.engines.sci.engine]kmath.obj,[.engines.sci.engine]kmenu.obj,[.engines.sci.engine]kmisc.obj,[.engines.sci.engine]kmovement.obj,[.engines.sci.engine]kparse.obj
    O_SCI_2 = [.engines.sci.engine]kpathing.obj,[.engines.sci.engine]kscripts.obj,[.engines.sci.engine]ksound.obj,[.engines.sci.engine]kstring.obj,[.engines.sci.engine]kvideo.obj,[.engines.sci.engine]message.obj,[.engines.sci.engine]object.obj,[.engines.sci.engine]savegame.obj,[.engines.sci.engine]script.obj,[.engines.sci.engine]script_patches.obj,[.engines.sci.engine]scriptdebug.obj,[.engines.sci.engine]seg_manager.obj,[.engines.sci.engine]segment.obj,[.engines.sci.engine]selector.obj,[.engines.sci.engine]state.obj,[.engines.sci.engine]static_selectors.obj,[.engines.sci.engine]vm.obj,[.engines.sci.engine]vm_types.obj,[.engines.sci.engine]workarounds.obj
    O_SCI_3 = [.engines.sci.graphics]animate.obj,[.engines.sci.graphics]cache.obj,[.engines.sci.graphics]compare.obj,[.engines.sci.graphics]controls16.obj,[.engines.sci.graphics]controls32.obj,[.engines.sci.graphics]coordadjuster.obj,[.engines.sci.graphics]cursor.obj,[.engines.sci.graphics]font.obj,[.engines.sci.graphics]fontsjis.obj,[.engines.sci.graphics]frameout.obj,[.engines.sci.graphics]maciconbar.obj,[.engines.sci.graphics]menu.obj,[.engines.sci.graphics]paint.obj,[.engines.sci.graphics]paint16.obj,[.engines.sci.graphics]paint32.obj,[.engines.sci.graphics]palette.obj,[.engines.sci.graphics]palette32.obj,[.engines.sci.graphics]picture.obj,[.engines.sci.graphics]portrait.obj
    O_SCI_4 = [.engines.sci.graphics]ports.obj,[.engines.sci.graphics]screen.obj,[.engines.sci.graphics]text16.obj,[.engines.sci.graphics]text32.obj,[.engines.sci.graphics]transitions.obj,[.engines.sci.graphics]view.obj,[.engines.sci.parser]grammar.obj,[.engines.sci.parser]said.obj,[.engines.sci.parser]vocabulary.obj,[.engines.sci.sound]audio.obj,[.engines.sci.sound]midiparser_sci.obj,[.engines.sci.sound]music.obj,[.engines.sci.sound]soundcmd.obj,[.engines.sci.sound.drivers]adlib.obj,[.engines.sci.sound.drivers]amigamac.obj,[.engines.sci.sound.drivers]cms.obj,[.engines.sci.sound.drivers]fb01.obj,[.engines.sci.sound.drivers]fmtowns.obj,[.engines.sci.sound.drivers]midi.obj
    O_SCI_5 = [.engines.sci.sound.drivers]pcjr.obj,[.engines.sci.video]robot_decoder.obj,[.engines.sci.video]seq_decoder.obj

--- AGI_ENG.OLB chunks (41 modules, all of [.engines.agi]) ---
    O_AGI_1 = [.engines.agi]agi.obj,[.engines.agi]checks.obj,[.engines.agi]console.obj,[.engines.agi]cycle.obj,[.engines.agi]detection.obj,[.engines.agi]font.obj,[.engines.agi]global.obj,[.engines.agi]graphics.obj,[.engines.agi]id.obj,[.engines.agi]inv.obj,[.engines.agi]keyboard.obj,[.engines.agi]loader_v1.obj,[.engines.agi]loader_v2.obj,[.engines.agi]loader_v3.obj,[.engines.agi]logic.obj,[.engines.agi]lzw.obj,[.engines.agi]menu.obj,[.engines.agi]motion.obj,[.engines.agi]objects.obj,[.engines.agi]op_cmd.obj,[.engines.agi]op_dbg.obj,[.engines.agi]op_test.obj,[.engines.agi]opcodes.obj,[.engines.agi]picture.obj,[.engines.agi]preagi.obj,[.engines.agi]preagi_mickey.obj
    O_AGI_2 = [.engines.agi]preagi_troll.obj,[.engines.agi]preagi_winnie.obj,[.engines.agi]saveload.obj,[.engines.agi]sound.obj,[.engines.agi]sound_2gs.obj,[.engines.agi]sound_coco3.obj,[.engines.agi]sound_midi.obj,[.engines.agi]sound_pcjr.obj,[.engines.agi]sound_sarien.obj,[.engines.agi]sprite.obj,[.engines.agi]systemui.obj,[.engines.agi]text.obj,[.engines.agi]view.obj,[.engines.agi]wagparser.obj,[.engines.agi]words.obj

----------------------------------------------------------------------------
Targets
----------------------------------------------------------------------------
    all : kyra.exe sierra.exe
    	@ write sys$output "KYRA.EXE and SIERRA.EXE build complete."

Individual targets, for when you only want one image:
  MMK /IGNORE=WARNING kyra.exe
  MMK /IGNORE=WARNING sierra.exe
and `MMK objects` to just compile everything without linking.
    objects : $(ALL_OBJS)
    	@ write sys$output "All objects up to date."

----------------------------------------------------------------------------
KYRA.EXE
----------------------------------------------------------------------------
Note what is NOT on the command line any more, compared with the single-library
version of this file: [.gui]debugger.obj, [.gui]saveload.obj and
[.engines.kyra]util.obj.  Those three were command-line objects ONLY to dodge
basename collisions inside the one old KYRA_OBJS.OLB; kyra now has its own
library, so they are ordinary library members again (see the O_GUI_2 note).
    kyra.exe : scumm_main.olb kyra_eng.olb [.base]plugins.obj -
    	[.backends.platform.sdl.posix]posix-main.obj [.gui]error.obj -
    	[.common]quicktime.obj x11_link.opt
    	@ write sys$output "Linking KYRA.EXE ..."
    	$(LINK)$(LINKFLAGS) /EXE=kyra.exe /MAP=kyra.map/FULL/CROSS_REFERENCE -
    	[.backends.platform.sdl.posix]posix-main.obj,-
    	[.base]plugins.obj,-
    	[.gui]error.obj,-
    	[.common]quicktime.obj,-
    	[]scumm_main.olb/LIBRARY,-
    	[]kyra_eng.olb/LIBRARY,-
    	[]scumm_main.olb/LIBRARY,-
    	[]kyra_eng.olb/LIBRARY,-
    	LIBSDL:LIBSDLMAIN/LIB,-
    	LIBSDL:LIBVIDEO/LIB,-
    	LIBSDL:LIBVIDEO_X11/LIB,-
    	LIBSDL:LIBJOYSTICK/LIB,-
    	LIBSDL:LIBEVENTS/LIB,-
    	LIBSDL:LIBFILE/LIB,-
    	LIBSDL:LIBAUDIO/LIB,-
    	LIBSDL:LIBCDROM/LIB,-
    	LIBSDL:LIBTIMER/LIB,-
    	LIBSDL:LIBCPUINFO/LIB,-
    	LIBSDL:LIBTHREAD/LIB,-
    	LIBSDL:LIBTHREAD_GENERIC/LIB,-
    	LIBSDL:LIBLOADSO/LIB,-
    	LIBSDL:STDLIB/LIB,-
    	LIBSDL:LIBSDL/LIB,-
    	[]x11_link.opt/OPTIONS

----------------------------------------------------------------------------
SIERRA.EXE  (AGI + SCI, including SCI32)
----------------------------------------------------------------------------
IDENTICAL to the KYRA.EXE rule except for three things:
  - [.base]plugins_sierra.obj replaces [.base]plugins.obj (that ONE object is
    what decides which engines get registered - see plugins_table.h)
  - sci_eng.olb + agi_eng.olb replace kyra_eng.olb
  - the map is SIERRA.MAP
The SDL/X11 library list is byte-for-byte the same; LIBSDL:LIBAUDIO/LIB is
required even though no audio device is ever opened (SDL_Init/SDL_Quit pull in
the audio subsystem's symbols regardless).
    sierra.exe : scumm_main.olb sci_eng.olb agi_eng.olb [.base]plugins_sierra.obj -
    	[.backends.platform.sdl.posix]posix-main.obj [.gui]error.obj -
    	[.common]quicktime.obj x11_link.opt
    	@ write sys$output "Linking SIERRA.EXE ..."
    	$(LINK)$(LINKFLAGS) /EXE=sierra.exe /MAP=sierra.map/FULL/CROSS_REFERENCE -
    	[.backends.platform.sdl.posix]posix-main.obj,-
    	[.base]plugins_sierra.obj,-
    	[.gui]error.obj,-
    	[.common]quicktime.obj,-
    	[]scumm_main.olb/LIBRARY,-
    	[]sci_eng.olb/LIBRARY,-
    	[]agi_eng.olb/LIBRARY,-
    	[]scumm_main.olb/LIBRARY,-
    	[]sci_eng.olb/LIBRARY,-
    	[]agi_eng.olb/LIBRARY,-
    	LIBSDL:LIBSDLMAIN/LIB,-
    	LIBSDL:LIBVIDEO/LIB,-
    	LIBSDL:LIBVIDEO_X11/LIB,-
    	LIBSDL:LIBJOYSTICK/LIB,-
    	LIBSDL:LIBEVENTS/LIB,-
    	LIBSDL:LIBFILE/LIB,-
    	LIBSDL:LIBAUDIO/LIB,-
    	LIBSDL:LIBCDROM/LIB,-
    	LIBSDL:LIBTIMER/LIB,-
    	LIBSDL:LIBCPUINFO/LIB,-
    	LIBSDL:LIBTHREAD/LIB,-
    	LIBSDL:LIBTHREAD_GENERIC/LIB,-
    	LIBSDL:LIBLOADSO/LIB,-
    	LIBSDL:STDLIB/LIB,-
    	LIBSDL:LIBSDL/LIB,-
    	[]x11_link.opt/OPTIONS

----------------------------------------------------------------------------
The four object libraries
----------------------------------------------------------------------------
The objects are packaged into object LIBRARIES rather than fed to LINK directly
(DCL's 4095-char command line would overflow) or via
an /OPTIONS file.  The options-file route resolved all cross-references but
forced posix-main.obj to be placed alongside them, and the OpenVMS linker does
not promote a transfer address from an /OPTIONS-file object (%LINK-W-USRTFR).
Splitting posix-main.obj out onto the command line fixed the transfer address
but then CXXLINK failed to resolve posix-main's C++ references (Common::String,
g_system, scummvm_main, OSystem_POSIX) against the opt-file objects
(%LINK-W-NUDFSYMS).  An object library resolves BOTH: posix-main.obj stays on
the command line (transfer address preserved) and its references pull the
defining modules out of the library into the same cluster.  This is exactly
how ScummVM links statically elsewhere; the plugins object holds a HARD
reference to each enabled engine's registrator (g_KYRA_getObject / g_KYRA_type,
g_SCI_*, g_AGI_*), so the linker force-extracts that engine's detection module
and the rest of the engine follows - it is NOT silently dropped.  This is also
exactly why the two images differ ONLY in which plugins object is on the
command line: no reference to g_KYRA_* means KYRA_ENG.OLB is never searched
productively, and vice versa.  Each LIBRARY/INSERT
line reuses a <=700-char O_* chunk (under the MMK ~1024-char element limit).
posix-main.obj is intentionally NOT inserted here (see O_BACKENDS_2 note).
LIBRARY/CREATE MUST BE EXPLICITLY PRESIZED - the defaults ACCVIO on this tree.
A bare `library /create /object` uses the librarian defaults, documented as
BLOCKS:100, MODULES:256, GLOBALS:1000, KEYSIZE:31, HISTORY:20.  Those are wildly
undersized for a tree this size.  In the ORIGINAL single-library layout (one
KYRA_OBJS.OLB holding all 358 modules) the librarian died with ~217 modules
inserted, part way through what was then $(O_ENGINES_3):
  %SYSTEM-F-ACCVIO, reason mask=00, virtual address=0000000000074104, PC=7AFA2FC4
Note the PC is NOT in CXX$COMPILER and there is no GEM_* frame - this is the
LIBRARIAN faulting, NOT another compiler bug.  Every one of those objects had
already compiled fine.  Read the PC before assuming which tool crashed.
GLOBALS:1000 is the binding limit, not MODULES:256 - a C++ tree emits many global
symbols per module (every non-inline method, vtable and typeinfo), so 217 modules
blow past 1000 global entries long before the module count matters.  The librarian
is supposed to extend the index on demand and instead faults, so presize it
generously here; over-allocating an index costs only disk in the .OLB.
THE LIBRARY IS BUILT UNDER A TEMP NAME AND RENAMED AT THE END.  DO NOT "SIMPLIFY"
THIS BY INSERTING STRAIGHT INTO THE FINAL .OLB.  Reason: if any LIBRARY/INSERT dies
part way through (as the librarian ACCVIO above did, at ~module 217), MMK stops - but
it does NOT delete the half-written target.  That partial .OLB is NEWER than all its
objects, so the NEXT `mmk` considers it up to date, skips this rule entirely (no
"Building object library ..." line appears), and hands the linker a library missing
every module after the crash point.  That produced
  %LINK-W-NUDFSYMS, 629 undefined symbols
whose members were ALL defined from the crash chunk onward - Kyra Screen*/script*/
sequences*/sound*/staticres*/text*/timer*, every GUI::*, Graphics::Surface/Font/
CursorManager, the scalers (HQ2x, AdvMame2x, _2xSaI, InitScalers, stretch200To240),
the thumbnail helpers and Image::BitmapDecoder - with NOTHING missing from the
chunks inserted BEFORE the crash point.  That signature (undefined symbols starting
abruptly at one chunk boundary and never stopping) means a TRUNCATED LIBRARY, not a
source or symbol-visibility problem.  Do not go looking for missing #includes.
Building to <name>_TMP.OLB and renaming only on success makes the target atomic:
a failed run leaves either no .OLB or the previous good one, never a newer partial.
All four rules below follow that pattern.

NOTE ON THE DEPENDENCY LISTS: scumm_main.olb depends on $(MAIN_OBJS), which
INCLUDES the five objects that are not inserted into it (posix-main, both
plugins objects, gui/error, common/quicktime).  That is a deliberate
over-dependency, not an oversight: it keeps "the library is newer than every
shared object" true, and it costs only a re-run of the librarian on the rare
occasion one of those five changes.
    scumm_main.olb : $(MAIN_OBJS)
    	@ write sys$output "Building object library SCUMM_MAIN.OLB (245 modules) ..."
    	- delete []scumm_main_tmp.olb;*
    	library /create=(blocks:4000,modules:1000,globals:60000) /object scumm_main_tmp.olb
    	library /insert scumm_main_tmp.olb $(O_AUDIO_1)
    	library /insert scumm_main_tmp.olb $(O_AUDIO_2)
    	library /insert scumm_main_tmp.olb $(O_AUDIO_3)
    	library /insert scumm_main_tmp.olb $(O_BACKENDS_1)
    	library /insert scumm_main_tmp.olb $(O_BACKENDS_2)
    	library /insert scumm_main_tmp.olb $(O_BASE_1)
    	library /insert scumm_main_tmp.olb $(O_COMMON_1)
    	library /insert scumm_main_tmp.olb $(O_COMMON_2)
    	library /insert scumm_main_tmp.olb $(O_ENGINES_1)
    	library /insert scumm_main_tmp.olb $(O_GRAPHICS_1)
    	library /insert scumm_main_tmp.olb $(O_GRAPHICS_2)
    	library /insert scumm_main_tmp.olb $(O_GUI_1)
    	library /insert scumm_main_tmp.olb $(O_GUI_2)
    	library /insert scumm_main_tmp.olb $(O_IMAGE_1)
    	library /insert scumm_main_tmp.olb $(O_VIDEO_1)
    	@ write sys$output "Library built OK - promoting to SCUMM_MAIN.OLB ..."
    	- delete []scumm_main.olb;*
    	rename []scumm_main_tmp.olb []scumm_main.olb
    	@ write sys$output "SCUMM_MAIN.OLB ready."

    kyra_eng.olb : $(KYRA_ENG_OBJS)
    	@ write sys$output "Building object library KYRA_ENG.OLB (109 modules) ..."
    	- delete []kyra_eng_tmp.olb;*
    	library /create=(blocks:2500,modules:600,globals:40000) /object kyra_eng_tmp.olb
    	library /insert kyra_eng_tmp.olb $(O_KYRA_1)
    	library /insert kyra_eng_tmp.olb $(O_KYRA_2)
    	library /insert kyra_eng_tmp.olb $(O_KYRA_3)
    	library /insert kyra_eng_tmp.olb $(O_KYRA_4)
    	library /insert kyra_eng_tmp.olb $(O_KYRA_5)
    	@ write sys$output "Library built OK - promoting to KYRA_ENG.OLB ..."
    	- delete []kyra_eng.olb;*
    	rename []kyra_eng_tmp.olb []kyra_eng.olb
    	@ write sys$output "KYRA_ENG.OLB ready."

    sci_eng.olb : $(SCI_ENG_OBJS)
    	@ write sys$output "Building object library SCI_ENG.OLB (82 modules) ..."
    	- delete []sci_eng_tmp.olb;*
    	library /create=(blocks:2500,modules:600,globals:40000) /object sci_eng_tmp.olb
    	library /insert sci_eng_tmp.olb $(O_SCI_1)
    	library /insert sci_eng_tmp.olb $(O_SCI_2)
    	library /insert sci_eng_tmp.olb $(O_SCI_3)
    	library /insert sci_eng_tmp.olb $(O_SCI_4)
    	library /insert sci_eng_tmp.olb $(O_SCI_5)
    	@ write sys$output "Library built OK - promoting to SCI_ENG.OLB ..."
    	- delete []sci_eng.olb;*
    	rename []sci_eng_tmp.olb []sci_eng.olb
    	@ write sys$output "SCI_ENG.OLB ready."

    agi_eng.olb : $(AGI_ENG_OBJS)
    	@ write sys$output "Building object library AGI_ENG.OLB (41 modules) ..."
    	- delete []agi_eng_tmp.olb;*
    	library /create=(blocks:1500,modules:400,globals:20000) /object agi_eng_tmp.olb
    	library /insert agi_eng_tmp.olb $(O_AGI_1)
    	library /insert agi_eng_tmp.olb $(O_AGI_2)
    	@ write sys$output "Library built OK - promoting to AGI_ENG.OLB ..."
    	- delete []agi_eng.olb;*
    	rename []agi_eng_tmp.olb []agi_eng.olb
    	@ write sys$output "AGI_ENG.OLB ready."

----------------------------------------------------------------------------
Header dependencies
----------------------------------------------------------------------------
WHY THIS SECTION EXISTS - the build had NO header dependencies at all, so
editing a header recompiled NOTHING and MMK reported every object up to date.
That silently produced a MIXED image: the common/singleton.h rework (which
deletes the Singleton<T>::_singleton static data member outright on __VMS and
turns DECLARE_SINGLETON into a no-op) left 50+ objects still compiled against
the OLD header, so they referenced a symbol that the new header no longer
defines anywhere -> 11 x %LINK-W-NUDFSYMS for
"Common::Singleton<X> ::_singleton", from a tree that "built clean".
The give-away was that config-manager.obj was the ONLY module still DEFINING
one (ConfigManager), because it is the one DECLARE_SINGLETON site built
/NOOPTIMIZE - i.e. the mix was an artefact of WHEN each object was compiled,
not of any source condition.  A stale-object bug of this shape costs hours to
diagnose from link errors, so the dependency is now declared.

GLOBAL_HDRS is deliberately SHORT - only the headers that (a) practically every
TU includes and (b) have needed PORT EDITS on OpenVMS, which is exactly the
class of edit that caused the problem:
  config.h        - the build's own configuration
  scummsys.h      - included by essentially every TU; has __VMS branches
  singleton.h     - the _singletonRef() rework described above
  hashmap.h       - USE_HASHMAP_MEMORY_POOL undef on __VMS
  endian.h        - byte-swap paths / SHIFTCNTBIG
  str.h / ustr.h  - Common::String, in nearly every interface
  system.h        - OSystem / g_system
  types.h         - fundamental typedefs
It is NOT a full #include closure (that would need thousands of lines and a
generator).  Editing a header NOT listed here still requires `MMK CLEAN` -
see the WARNING at the top of the file.  Adding more headers here is safe; the
only limit is the ~1024-char MMK action/dependency-line element cap, and the
longest line below still comes to ~912 chars ($(O_AUDIO_1) at 699 plus
$(GLOBAL_HDRS) at 210, after memory.h/algorithm.h/array.h were added on
2026-07-26 - the new engine chunks are all shorter, the longest being
$(O_KYRA_2) at 691), so there is roughly 110 chars of headroom left - i.e. ONE more
average-length header path.  If you need more than that, SPLIT A CHUNK first;
do not just append and hope.

The dependencies are declared per O_* CHUNK rather than per object: the chunk
macros already exist, are already <=700 chars, and already cover 480 of the 482
objects exactly once.  The two exceptions are in no chunk and get their own
lines below: posix-main.obj and plugins_sierra.obj (both are command-line
objects, not library members).  These are dependency-ONLY lines with NO
action, so each object still builds via the .CPP.OBJ suffix rule, and objects
that ALSO have an explicit /NOOPTIMIZE rule above keep that rule's action -
MMK merges the dependencies from a no-action line with the action from the
explicit rule.
memory.h / algorithm.h / array.h were ADDED to this list on 2026-07-26.  They carry
the OpenVMS workaround for the HP C++ V7.3-009 LEVEL=3 miscompilation of the
"*dst++ = *first++" copy-loop shape (see the long note on uninitialized_copy in
common/memory.h).  They belong here by the same rule as the rest: array.h is pulled
in by practically every TU (directly or via hashmap.h / list.h / str.h), and these
three have now needed a PORT EDIT - which is exactly the class of edit that makes a
missing dependency produce a silently MIXED image.  Getting this wrong here is
especially nasty because the affected function is an INLINE TEMPLATE: a stale object
does not fail to link, it just keeps the miscompiled loop and crashes at run time.

CANDIDATE NOT ADDED - [.backends.platform.sdl]sdl-sys.h.  It meets both criteria
for this list (reached by ~10 backend headers, and it carries a PORT EDIT - the
`#pragma member_alignment` that undoes the struct packing the VMS SDL 1.2 headers
leak), and through sdl.h it reaches common/system.h, so it decides the layout of
class OSystem.  A stale mix there is an ODR violation that does not fail to link,
it just yields garbage vptr reads at run time.
It is deliberately LEFT OUT anyway: adding it invalidates all 482 objects, and a
full rebuild costs ~8 HOURS on the es40 emulator.  The cost of the safety net
exceeds the cost of the bug it prevents, given that the tree is currently known
clean.  Cost if added, for the record: 34 chars, longest dependency line 912 ->
946 against the ~1024 cap (~78 chars headroom), so no chunk split is needed.
ADDED 2026-08-28, on exactly that reasoning: raising $(OPT) to LEVEL=4 forces a
full rebuild anyway, so the one-time cost of listing sdl-sys.h is zero and the
ODR hazard above is closed for good.  Measured after adding it: $(GLOBAL_HDRS)
expands to 244 chars and the longest dependency line ($(O_AUDIO_1) : ...) to 943,
against the ~1024 MMK action-element cap - about 81 chars of headroom left.
    GLOBAL_HDRS = []config.h,[.common]scummsys.h,[.common]singleton.h,-
    [.common]hashmap.h,[.common]endian.h,[.common]str.h,[.common]ustr.h,-
    [.common]system.h,[.common]types.h,[.common]memory.h,[.common]algorithm.h,-
    [.common]array.h,[.backends.platform.sdl]sdl-sys.h

    $(O_AUDIO_1) : $(GLOBAL_HDRS)
    $(O_AUDIO_2) : $(GLOBAL_HDRS)
    $(O_AUDIO_3) : $(GLOBAL_HDRS)
    $(O_BACKENDS_1) : $(GLOBAL_HDRS)
    $(O_BACKENDS_2) : $(GLOBAL_HDRS)
    [.backends.platform.sdl.posix]posix-main.obj : $(GLOBAL_HDRS)
    $(O_BASE_1) : $(GLOBAL_HDRS)
    [.base]plugins_sierra.obj : $(GLOBAL_HDRS)
    $(O_COMMON_1) : $(GLOBAL_HDRS)
    $(O_COMMON_2) : $(GLOBAL_HDRS)
    $(O_ENGINES_1) : $(GLOBAL_HDRS)
    $(O_GRAPHICS_1) : $(GLOBAL_HDRS)
    $(O_GRAPHICS_2) : $(GLOBAL_HDRS)
    $(O_GUI_1) : $(GLOBAL_HDRS)
    $(O_GUI_2) : $(GLOBAL_HDRS)
    $(O_IMAGE_1) : $(GLOBAL_HDRS)
    $(O_VIDEO_1) : $(GLOBAL_HDRS)
    $(O_KYRA_1) : $(GLOBAL_HDRS)
    $(O_KYRA_2) : $(GLOBAL_HDRS)
    $(O_KYRA_3) : $(GLOBAL_HDRS)
    $(O_KYRA_4) : $(GLOBAL_HDRS)
    $(O_KYRA_5) : $(GLOBAL_HDRS)
    $(O_SCI_1) : $(GLOBAL_HDRS)
    $(O_SCI_2) : $(GLOBAL_HDRS)
    $(O_SCI_3) : $(GLOBAL_HDRS)
    $(O_SCI_4) : $(GLOBAL_HDRS)
    $(O_SCI_5) : $(GLOBAL_HDRS)
    $(O_AGI_1) : $(GLOBAL_HDRS)
    $(O_AGI_2) : $(GLOBAL_HDRS)

----------------------------------------------------------------------------
The second compile of base/plugins.cpp
----------------------------------------------------------------------------
There is no PLUGINS_SIERRA.CPP - this object is base/plugins.cpp compiled a
SECOND time with STANDALONE_TARGET_SIERRA added, so engines/plugins_table.h
emits LINK_PLUGIN(AGI)+LINK_PLUGIN(SCI) instead of LINK_PLUGIN(KYRA).  The
generic .CPP.OBJ suffix rule cannot make this target (wrong basename, and it
would use the wrong /DEFINE), so it needs an explicit rule with an explicit
source.  $(CXXFLAGS_SIERRA) is a COMPLETE copy of $(CXXFLAGS) with the extra
define appended - a second /DEFINE= on a CXX command line REPLACES the first
one rather than merging with it, so the whole list has to be repeated.

Both plugins objects deliberately stay OUT of SCUMM_MAIN.OLB: they define the
same global symbols, so the librarian would reject the second insert, and the
linker must be handed exactly one of them per image.  See the kyra.exe and
sierra.exe rules - each names its own plugins object on the LINK line.
    [.base]plugins_sierra.obj : [.base]plugins.cpp
    	$(CXX) $(CXXFLAGS_SIERRA) /OBJECT=$(MMS$TARGET) [.base]plugins.cpp

DECwindows / Motif shareables required by SDL 1.2's X11 video driver.
    x11_link.opt :
    	@ open/write optfile x11_link.opt
    	@ write optfile "SYS$SHARE:DECW$XLIBSHR.EXE/SHARE"
    	@ write optfile "SYS$SHARE:DECW$XTLIBSHRR5.EXE/SHARE"
    	@ write optfile "SYS$SHARE:DECW$XMULIBSHRR5.EXE/SHARE"
    	@ write optfile "SYS$SHARE:DECW$XEXTLIBSHR.EXE/SHARE"
    	@ write optfile "SYS$SHARE:DECW$XMLIBSHR12.EXE/SHARE"
    	@ close optfile

WARNING: a full rebuild from clean takes about EIGHT HOURS on the es40 EMULATOR.
On real AlphaServer hardware it is a small fraction of that, so the old blanket
"never MMK CLEAN" rule is now advice rather than law - and the one change that
genuinely needs it is a change to $(OPT) (see the warning near the top of this
file: MMK does not treat DESCRIP.MMS as a prerequisite, so nothing rebuilds by
itself when the optimization level changes).  Still do not run it to "be safe":
if you only changed a header that is not in $(GLOBAL_HDRS), delete just the
objects that include it.
    clean :
    	@ write sys$output "Cleaning objects ..."
    	- delete [...]*.obj;*
    	- delete []x11_link.opt;*
    	- delete []scumm_main.olb;*
    	- delete []kyra_eng.olb;*
    	- delete []sci_eng.olb;*
    	- delete []agi_eng.olb;*
    	- delete []scumm_main_tmp.olb;*
    	- delete []kyra_eng_tmp.olb;*
    	- delete []sci_eng_tmp.olb;*
    	- delete []agi_eng_tmp.olb;*
    	- delete []kyra_objs.olb;*
    	- delete []kyra.map;*
    	- delete []kyra.exe;*
    	- delete []sierra.map;*
    	- delete []sierra.exe;*
    	@ write sys$output "Clean complete."

----------------------------------------------------------------------------
Files needing /NOOPTIMIZE
----------------------------------------------------------------------------
These files crash the HP C++ Alpha back-end optimizer (ACCVIO inside
CXX$COMPILER, GEM_DF/CSE dataflow pass) at /OPTIMIZE=LEVEL=3.  It is a
deterministic compiler bug - re-running MMK does not help - so each such file
gets an explicit rule using $(CXXFLAGS_NOOPT).  An explicit target rule takes
precedence over the generic .CPP.OBJ suffix rule below.

  surfacesdl-graphics.cpp : ACCVIO in GEM_DF/CSE (optimizer CSE dataflow).
  default-saves.cpp       : ACCVIO in the back-end optimizer.
  config-manager.cpp      : %GEM-F-ASSERTION in GEM_DF_DATAFLOW (see below).
  chargen.cpp             : ACCVIO in GEM_CD <- GEM_CG (codegen, see below).
  (VectorRendererSpec.cpp was listed here for a long time and IS NOT IN THIS CLASS.
   /NOOPTIMIZE was what BROKE it: with plain /OPTIMIZE=(LEVEL=3,TUNE=HOST) it
   compiles.  It now uses the generic suffix rule - see the entry below, and read
   the /NOOPTIMIZE IS NOT FREE warning at the end of this comment block.)

The fault may present as either %SYSTEM-F-ACCVIO or %GEM-F-ASSERTION, and in a
GEM_DF_* (optimizer dataflow) or GEM_CG/GEM_CD (code generator) frame; what
identifies the class is that the frame is reached from GEM_CO_COMPILE_ROUTINE,
with NO ME_DEBUGGEN frame present.  Contrast the OTHER assertion classes, which
/NOOPTIMIZE does NOT fix: GEM_CX_EXTEND_LIFETIME and GEM_ST_GET_NAME/
GEM_OM_WRITE_GLOBALS (global-symbol emission at module finalization - that one
needs the symbol removed at source, see singleton.h).

READ THE PASS NAME - IT TELLS YOU WHICH LEVER TO PULL.  GEM_* frames are named
after the pass, and the pass determines the fix.  Quick key for this tree:
  GEM_LU_MAIN / PEEL_LOOP   loop unroller / peeler.  Runs only at LEVEL>1, so
                            /OPTIMIZE=LEVEL=1 REMOVES the pass.  Prefer LEVEL=1
                            over /NOOPTIMIZE.  See cdtoons.cpp.
  GEM_DF_* / CSE            optimizer dataflow.  Runs at LEVEL>1, so -> LEVEL=1
                            first, /NOOPTIMIZE only if that is not enough.  The
                            original class this section was for.  (This line used
                            to say "LEVEL=3-only"; that was just the tree default
                            at the time, not a property of the pass.)
  GEM_CG / GEM_CD           code generator proper -> /NOOPTIMIZE.
  GEM_CX_*                  CODE EXPANDER.  Runs at EVERY level, so /NOOPTIMIZE
                            CANNOT fix it and may CAUSE it - TRY PLAIN /OPTIMIZE
                            FIRST.  See the boxed warning below.
  ME_DEBUGGEN / walk_*      debug-symbol emission -> /NODEBUG/NOTRACEBACK.
                            Level-independent; do not touch the optimizer.
  GEM_ST_GET_NAME /         global-symbol emission at module finalization.  No
  GEM_OM_WRITE_GLOBALS      flag helps; remove the symbol at source (singleton.h).
A pass that only runs above a given level can always be sidestepped by dropping
TO that level - which is cheaper and less risky than turning optimization off.

****************************************************************************
/NOOPTIMIZE IS NOT FREE - IT CAN CAUSE CRASHES OF ITS OWN.  Established
2026-07-26 on graphics/VectorRendererSpec.cpp, which failed on SEVEN consecutive
builds with %GEM-F-ASSERTION in GEM_CX_EXTEND_LIFETIME and compiled FIRST TRY at
plain /OPTIMIZE=(LEVEL=3,TUNE=HOST).  The only variable changed was the flag.

WHY, and it is not mysterious: GEM_CX is the CODE EXPANDER and runs at EVERY
optimization level, so /NOOPTIMIZE can never fix a GEM_CX crash - but it can
cause one.  At /NOOPTIMIZE nothing is inlined away and nothing is live-range
split, so ONE routine's expanded body and its live-variable set are at their
LARGEST exactly when the per-routine expander has to walk them.  Optimization
SHRINKS what that pass sees.  The same lever appears run the other way in the
winexe_pe.cpp entry below, where /NOOPTIMIZE is wanted BECAUSE it stops inlining
from multiplying what a per-routine pass must walk.  Which direction helps
depends on the pass, so it is an EXPERIMENT, never a default.

CONSEQUENCES FOR THIS FILE - do not skip these:
 * NEVER add /NOOPTIMIZE "as a precaution" or "because the file is big".  Add it
   only against an observed GEM_DF_*/GEM_CG/GEM_CD traceback that it is shown to
   fix.  A precautionary /NOOPTIMIZE cost this port SEVEN rounds.
 * On any GEM_CX_* crash, TRY PLAIN /OPTIMIZE FIRST.  It is one line and one
   file, and it is now the highest-prior fix for that pass.
 * The remaining /NOOPTIMIZE entries below are SUSPECT to the extent they were
   added defensively rather than against a reproduced traceback.  screen.cpp and
   screen_delta.cpp both say "as a precaution" in their own notes - if either
   ever misbehaves, test plain /OPTIMIZE before reshaping any source.
****************************************************************************

CORRECTED TAXONOMY - two long-standing errors in these notes, both fixed 2026-07-26:
 (a) GEM_CX_EXTEND_LIFETIME is NOT "template instantiation".  GEM is the
     LANGUAGE-INDEPENDENT back end; GEM_CX is its code expander and "extend
     lifetime" is a VARIABLE LIVE-RANGE pass.  Fix by shortening live ranges or
     removing the routine - NOT by de-templating.  The one CONFIRMED case in this
     tree is eobcommon.cpp (36 push_backs in one ctor, below), fixed at source.
     AND THE FIRST THING TO TRY IS PLAIN /OPTIMIZE: optimization SHRINKS the live
     set this pass walks, so /NOOPTIMIZE can be the cause.  That is exactly what
     VectorRendererSpec.cpp turned out to be (2026-07-26) - it compiles at LEVEL=3
     and failed on all seven /NOOPTIMIZE builds.  See the boxed warning above.
     VectorRendererSpec.cpp was ALSO listed here on the strength of fp_sqroot's
     five `register` uint32s - that was WRONG, so treat "long-lived `register`
     locals" as an untested hypothesis, not an established trigger.
     Counter-evidence: hq2x.cpp:106 and hq3x.cpp:109
     each declare NINE `register int`s and compile fine at LEVEL=3.
 (b) generate_one_instantiation_output_file is NOT a template indicator.  It is the
     EDG driver walking the module and appears even in TUs with ZERO templates
     (proven: VectorRendererSpec.cpp kept that frame after full de-templating).
     Do not use its presence or absence to classify a crash.  The frame that
     identifies the target is GEM_CO_COMPILE_ROUTINE - it means ONE routine, so
     the first question is always WHICH routine, not which template.
 (c) A THIRD class exists that is not a compiler bug in any file: PHANTOM
     DIAGNOSTICS from the es40 emulator.  A file that is provably valid draws a
     nonsense front-end error, and a plain re-run clears it.  Seen as
     %SYSTEM-F-ACCVIO in GEM_CP_VMS/PARSE_BOOL_QUAL, and on 2026-07-26 as
       %CXX-E-EXPDECL, expected a declaration at line number 21 in file
       ...[SRC.KYRA-VMS.SRC.GRAPHICS]PIXELFORMAT.H;1
     while compiling [.BACKENDS.MIXER.NULLMIXER]NULLSDL-MIXER.CPP - line 21 being
     the ` */` that closes the stock licence block.
     HOW TO RECOGNISE IT IN ONE STEP, without inspecting anything: ask how many
     TUs already included that header EARLIER IN THE SAME RUN.  pixelformat.h is
     pulled in by common/system.h:29, so ~250 files had already parsed it
     successfully before nullsdl-mixer.cpp.  A header cannot be malformed on its
     250th inclusion and sound on the first 249 - so the file is fine and the
     diagnostic is noise.  That argument is worth more than any byte-level check
     of the file (we did the byte-level check too: 9 delimiter lines at 1, 21, 31,
     122, 135, 137, 139, 140, 238, identical to the local copy).
     FIX: just re-run - @BUILD_RETRY does it automatically and stops on its own if
     the failure turns out to be deterministic after all.
     Corollary: a stray `*/` can only be an error if the comment was ALREADY
     closed; an UNterminated /* in an earlier header would swallow it silently.
     So "unterminated comment upstream" is never the explanation for EXPDECL on a
     `*/`, which is what makes the phantom reading the strong one.
     COST NOTE: chasing this one burned several round-trips on a file-corruption
     theory (truncated long records).  Two facts killed it: config.h was intact,
     and 690 of 694 files exceed 80 columns - clipping at 80 would have broken the
     build nearly everywhere, not at one header.  Check the include-count argument
     FIRST next time.

NOTE: eobcommon.cpp was briefly listed here (ACCVIO in GEM_SR), but that crash
AND a second /NOOPTIMIZE-proof %GEM-F-ASSERTION in GEM_CX_EXTEND_LIFETIME both
stemmed from the same Common::Array<const int16*> _dscWallMapping (36 push_backs
in one ctor).  Fixed at source (eobcommon.h/.cpp: plain C array), so it now
builds with normal /OPTIMIZE and needs no rule here.

NOTE: advancedDetector.cpp was previously listed here, but its failure is a
DIFFERENT crash class - %GEM-F-ASSERTION in GEM_CX_EXTEND_LIFETIME (a code-expander
live-range crash, not an optimizer dataflow crash; the
generate_one_instantiation_output_file frame it also carried is NOT meaningful, see
the corrected taxonomy above).  /NOOPTIMIZE does NOT fix it.  The real fix
is in common/hashmap.h (undef USE_HASHMAP_MEMORY_POOL on __VMS), so
advancedDetector.cpp now builds with the normal /OPTIMIZE rule.
----------------------------------------------------------------------------
    [.backends.graphics.surfacesdl]surfacesdl-graphics.obj : -
    [.backends.graphics.surfacesdl]surfacesdl-graphics.cpp
    	$(CXX) $(CXXFLAGS_NOOPT) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

    [.backends.saves.default]default-saves.obj : -
    [.backends.saves.default]default-saves.cpp
    	$(CXX) $(CXXFLAGS_NOOPT) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

config-manager.cpp : class-(a) optimizer crash - %GEM-F-ASSERTION in
GEM_DF_DATAFLOW <- GEM_CO_COMPILE_ROUTINE at /OPTIMIZE=(LEVEL=3,TUNE=HOST), i.e.
the optimizer's dataflow pass on ONE routine (an assertion here rather than the
ACCVIO the other class-(a) files gave, but the same pass and the same fix).
Appeared only after the singleton.h rework replaced the `_singleton` data member
with the `_singletonRef()` function-local static: ConfigManager::defragment now
makes three calls returning a reference to that static, which is evidently what
the dataflow pass chokes on.  NOT a source error - the same header compiles fine
in archive.cpp and elsewhere; config-manager.cpp is the only TU that touches
_singletonRef() directly.  /NOOPTIMIZE, like the other class-(a) files.
    [.common]config-manager.obj : -
    [.common]config-manager.cpp
    	$(CXX) $(CXXFLAGS_NOOPT) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

staticres.cpp : class-(a) optimizer crash - %SYSTEM-F-ACCVIO in GEM_DF/CSE, with
the CSE dataflow frame recursing ~40 deep (blows the stack walking one huge
expression) at /OPTIMIZE=LEVEL=3.  This file is nothing but enormous static
resource tables (giant array initializers), exactly the input that makes CSE
recurse to death; there is nothing to optimize in static data anyway.  Same class
as surfacesdl-graphics / default-saves -> /NOOPTIMIZE.
    [.engines.kyra]staticres.obj : -
    [.engines.kyra]staticres.cpp
    	$(CXX) $(CXXFLAGS_NOOPT) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

screen_delta.cpp : Screen::decodeFrameDelta / decodeFrameDeltaPage (+ wrapped_*
workers) were MOVED OUT of screen.cpp into this dedicated TU to escape an HP C++
Alpha back-end codegen crash on wrapped_decodeFrameDeltaPage.  In screen.cpp the
crash walked from pass to pass as the code was reshaped - GEM_CX_EXTEND_LIFETIME
(address-taken-locals lifetime) <-> GEM_FG_FIND_BRANCH/DELETE_NULL_BLOCKS (CFG of
a while(1){...break}) - and fired during that big file's template-instantiation
FLUSH (traceback frame generate_one_instantiation_output_file), i.e. an INTERACTION
with screen.cpp's many Common::Array/List instantiations, not the routine alone.
The source workarounds (de-template noXor; one DeltaPageState struct = a single
address-taken local; loop body extracted to bool deltaPageStep() so the caller is
`while (step()){}` with no break) are retained IN screen_delta.cpp, and isolating
them in a small TU that instantiates no Common containers lets the back-end emit
them.  This is a new port-specific file (object #358 of the original 358).

2026-07-29 - TRYING /OPTIMIZE=LEVEL=1 HERE (was /NOOPTIMIZE).  Reason: PERF.
These two routines are HALF of the per-WSA-frame intro pipeline; wsamovie.cpp:118
calls Screen::decodeFrame4 (in screen.cpp) and then :153/:155 call
decodeFrameDelta / decodeFrameDeltaPage from THIS file, for EVERY frame.  Moving
screen.cpp from /NOOPTIMIZE to LEVEL=1 made the EOB2 intro only "a little faster"
- consistent with having optimized just the first of the two stages.

WHY THIS IS A REASONABLE THING TO TRY, even though the /NOOPTIMIZE here was NOT
precautionary (unlike screen.cpp's): the crash it was added against was diagnosed
as an INTERACTION with screen.cpp's Common::Array/List template-instantiation
FLUSH, and the fix credited above is the SPLIT into a TU that instantiates no
Common containers, plus the three retained source workarounds.  The /NOOPTIMIZE
was applied in the same round as the split and was never tested INDEPENDENTLY of
it, so it may well be redundant now.  One round settles it.

IF IT CRASHES: put $(CXXFLAGS_NOOPT) back and record the pass name here - that
result is itself valuable, as it would prove the split alone was NOT sufficient.
Do NOT try LEVEL=3 on this file: GEM_CX_EXTEND_LIFETIME was one of the observed
passes and it is exactly the live-range pass that higher levels lean on hardest.
    [.engines.kyra]screen_delta.obj : -
    [.engines.kyra]screen_delta.cpp
    	$(CXX) $(CXXFLAGS_OPT1) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

chargen.cpp : class-(a) crash - %SYSTEM-F-ACCVIO in GEM_CD <- GEM_CG <-
GEM_CO_COMPILE_ROUTINE <- GEM_CO_COMPILE_MODULE at /OPTIMIZE=(LEVEL=3,TUNE=HOST).
GEM_CG is the code generator proper and the caller is GEM_CO_COMPILE_ROUTINE, i.e.
ORDINARY per-routine code generation.  Note what is ABSENT from the traceback: no
generate_one_instantiation_output_file frame (so NOT the template-instantiation
class that plagued screen.cpp) and no ME_DEBUGGEN frame (so NOT the debug-info
class that needed winexe_pe.cpp's /NODEBUG).  A codegen fault on one routine at
LEVEL=3 is the class /NOOPTIMIZE exists for.  The likely routine is
CharacterGenerator::finish() (~340 lines, lines 1140-1483, with nested static
tables including an array-of-pointers itemList[]), though as always the pass gives
no source line so this is not worth confirming.
    [.engines.kyra]chargen.obj : -
    [.engines.kyra]chargen.cpp
    	$(CXX) $(CXXFLAGS_NOOPT) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

VectorRendererSpec.cpp : SOLVED 2026-07-26 - /NOOPTIMIZE WAS THE CAUSE.

This file now has NO explicit rule: it builds via the generic .CPP.OBJ suffix rule at
plain $(OPT), which is where it belongs.  Nothing about the SOURCE was ever wrong.
Kept here as the cautionary entry for this whole section.  (It was proven clean at
LEVEL=3; it has not been retested at LEVEL=4.  If it asserts again, the FIRST thing
to check is that nobody has "helpfully" put /NOOPTIMIZE back on it.)

THE FAILURE: seven consecutive builds, always bit-identical,
  %GEM-F-ASSERTION in GEM_CX_EXTEND_LIFETIME <- GEM_CX_INTERP <- GEM_CX <- GEM_CG
                                             <- GEM_CO_COMPILE_ROUTINE
                                             <- GEM_CO_COMPILE_MODULE
THE FIX: delete the /NOOPTIMIZE rule.  One line, compiled first try.

WHY /NOOPTIMIZE CAUSED IT.  GEM_CX is the CODE EXPANDER; it runs at EVERY
optimization level, so /NOOPTIMIZE could never have FIXED this crash - but it can
CREATE it.  Unoptimized, nothing is inlined away and no live range is split, so one
routine's expanded body and live-variable set are at their MAXIMUM precisely when the
per-routine expander walks them.  Optimization SHRINKS that input.  (Contrast
winexe_pe.cpp below, where /NOOPTIMIZE is wanted BECAUSE it stops inlining from
multiplying what a per-routine pass must walk - the same lever, opposite direction.
Which way helps depends on the pass, so it is always an EXPERIMENT, never a default.)
The /NOOPTIMIZE here was never justified by evidence: it was added "as a LEVEL=3
precaution on a big AA-renderer module", self-documented as "NOT known to be
required".  That precaution WAS the bug.

HOW SEVEN ROUNDS WERE BURNED - the genuinely useful part of this entry.
  1. /NOOPTIMIZE  <-- present from the start; THE ACTUAL CAUSE, and the one thing
                      never questioned, because it was filed as a safe precaution
  2. #define DISABLE_FANCY_THEMES in config.h   3. colorFill's Duff's device
  4. drawString's temporaries                  5. de-templating the whole class
  6. guarding out fp_sqroot                    7. `#undef DISABLE_FANCY_THEMES`
Rounds 2 and 7 are the same variable pushed both ways, and BOTH failed - which alone
proves the macro was never causal.  Round 5 should also have ended it: you cannot
rewrite every routine in a module and get a byte-for-byte identical codegen failure
unless the real variable is OUTSIDE the source entirely.  It was: it was in the
COMMAND LINE.  Six rounds searched a file that had nothing wrong with it.

RULES LEARNED - apply these to the NEXT compiler crash in this port:
 * SUSPECT YOUR OWN BUILD FLAGS BEFORE THE SOURCE.  The command line is part of the
   input.  When every source reshape leaves a traceback IDENTICAL, stop editing
   source: an invariant output means you have not yet touched the real variable, and
   what stayed constant across all seven rounds was the flag.
 * NEVER add /NOOPTIMIZE (or any flag) "as a precaution" or "because the file is
   big".  An unjustified flag is indistinguishable from a bug and outlives the memory
   of why it was added.  Add it only against an observed traceback it is SHOWN to fix,
   and record the traceback next to it.
 * "ASK WHEN IT LAST COMPILED" only helps if you check the answer.  The prior note
   asserted this file "COMPILED CLEANLY before the macro" - there is NO record of
   VECTORRENDERERSPEC.OBJ ever having been produced.  It was a NEVER-COMPILED file
   misread as a regression, which is what made a phantom regression seem worth
   chasing.  Verify the claim; do not inherit it.
 * A one-variable experiment requires the variable to be ISOLATED.  config.h is in
   $(GLOBAL_HDRS), so rounds 2 and 7 rebuilt the world and proved nothing about this
   file.  Per-file FLAG changes are the cheapest truly isolated experiment available
   - which is why the flag should have been tested FIRST, not seventh.
 * GEM_CX_EXTEND_LIFETIME is a back-end VARIABLE LIVE-RANGE pass, NOT C++
   temporary-lifetime extension; GEM is shared with DEC C/Fortran/Pascal and has no
   notion of a C++ temporary.  On any GEM_CX_* crash, TRY PLAIN /OPTIMIZE FIRST.
 * generate_one_instantiation_output_file is NOT a template indicator - it is the EDG
   driver walking the module and appears in TUs with ZERO templates (proven in round
   5, after full de-templating).  Do not classify a crash by it.

SOURCE CHANGES RETAINED - none of them fixed anything, all are harmless, and two are
worth keeping on their own merits.  All are SAFE TO REVERT to reduce the diff:
 - colorFill() as a counted loop: fixes a GENUINE upstream out-of-bounds write - with
   last < first and a negative count that is a multiple of 8, `count % 8` selects
   `case 0:` and 8 pixels are written BEFORE `first`.  KEEP: real bug, real fix.
 - the removed dead `= Common::Rect(0,0,0,0)` default argument (the pure virtual in
   VectorRenderer.h declares no default; the sole caller passes all 8 args).  KEEP.
 - `register` dropped from fp_sqroot and common/math.h:101 - a no-op hint in C++, so
   it changed no generated code and was never a candidate.  Counter-evidence it was
   ever a hazard: hq2x.cpp:106 / hq3x.cpp:109 each declare NINE and build at LEVEL=3.
 - the de-templating (PixelType = `typedef uint16`).  NOT load-bearing.  Sound only
   because SurfaceSDL hard-codes a 16bpp overlay (surfacesdl-graphics.cpp:851/:860)
   and this header has exactly ONE includer; a compile-time assert in the .cpp pins
   that invariant.  Revert if 32bpp overlay support is ever needed.

The FILE SPLIT that was prepared as the fallback (the AA class is a contiguous
#ifndef block, and the BE_*/WU_* macro sets partition with zero crossover) was NOT
needed and was NOT done.  If some future crash does need it, note the real cost: six
shared members are `inline` in the .cpp and would have to move into the header.

screen.cpp : MOVED /NOOPTIMIZE -> /OPTIMIZE=LEVEL=1 on 2026-07-29 (PERF).
See the LEVEL=1 section below for the rule.  Kept here as a signpost because
this is where the /NOOPTIMIZE used to be.

----------------------------------------------------------------------------
Files needing /NOOPTIMIZE/NODEBUG/NOTRACEBACK  (debug-info generation crash)
----------------------------------------------------------------------------
A FIFTH crash class - see the $(CXXFLAGS_NODBG) comment above for the full
traceback, and for why it needs BOTH /NODEBUG/NOTRACEBACK (to suppress the pass)
AND /NOOPTIMIZE (to stop inlining from multiplying what the pass must walk).
Neither a re-run nor a source reshape helps.  The signature to look for is
ME_DEBUGGEN / me_generate_debug_information / walk_scope_variables in the
traceback: that is DEBUG-SYMBOL emission, not code generation.

  winexe_pe.cpp : %SYSTEM-F-ACCVIO in extend_memory_region <- allocate_hash_array
    <- create_hash_table <- initialize_me_debuggen.  The trigger is the TRIPLE-
    NESTED HashMap in common/winexe_pe.h:111-113 -
        LangMap = HashMap<WinResourceID, Resource, ...>
        NameMap = HashMap<WinResourceID, LangMap , ...>
        TypeMap = HashMap<WinResourceID, NameMap , ...>
    so TypeMap is HashMap<ID, HashMap<ID, HashMap<ID, Resource>>>.  Each level
    embeds the ENTIRE node/iterator/functor type tree of the level below, and
    PEResources::parseResourceLevel has locals of those types, so the debug TYPE
    graph the walker must emit grows combinatorially.  The debug-symbol hash
    table blows up trying to hold it.  Note this is a DEBUG-INFO size problem,
    NOT the usual HashMap codegen problem - the code itself compiles fine.
----------------------------------------------------------------------------
    [.common]winexe_pe.obj : -
    [.common]winexe_pe.cpp
    	$(CXX) $(CXXFLAGS_NODBG) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

----------------------------------------------------------------------------
Files needing /OPTIMIZE=LEVEL=1
----------------------------------------------------------------------------
Two reasons a file lands here.  (1) The LEVEL=3 GEM dataflow passes GRIND
pathologically (CPU-bound in a tiny PC range, static working set, no new page
faults) for an unbounded time - no crash, just a stall.  (2) The file crashes a
pass that only RUNS at high optimization levels, where dropping all the way to
/NOOPTIMIZE would be overkill (and, per the boxed warning in the NOOPT section,
carries its own risk).  LEVEL=1 keeps basic optimization in both cases.  See the
$(CXXFLAGS_OPT1) comment above.  An explicit target rule takes precedence over
the generic .CPP.OBJ suffix rule.

  cdtoons.cpp : %SYSTEM-F-ACCVIO in GEM_FG_CREATE_FLOW_NODE <-
    GEM_IL_GENERATE_FLND <- PEEL_LOOP <- GEM_LU_MAIN <- GEM_CO_COMPILE_ROUTINE.
    Reason (2), and the traceback names the culprit outright: GEM_LU is the LOOP
    UNROLLER and PEEL_LOOP is loop PEELING, which builds new flow nodes for the
    peeled iteration - it dies creating one.  Loop peeling is a LEVEL>1
    transformation, so LEVEL=1 removes the pass rather than working around it.
    The routine is CDToonsDecoder::renderBlock (line 351): a `for (y...)` whose
    inner `while (x < width && !done)` RLE decoder has THREE `continue` paths and
    mutates the shared cursor `currData` on every one.  Peeling one iteration out
    of that means cloning a body whose exits and carried pointer state all differ
    per path, which is where the flow-node builder falls over.
    NOTE: this file is BYTE-IDENTICAL to upstream ScummVM 1.8.1 - verified by
    diff, no port edits at all - so do NOT go looking for a source bug, and do
    NOT reshape the loop.  It is purely an optimizer-pass bug on valid C++.
----------------------------------------------------------------------------
    [.image.codecs]cdtoons.obj : -
    [.image.codecs]cdtoons.cpp
    	$(CXX) $(CXXFLAGS_OPT1) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

  screen.cpp : PROMOTED TO FULL $(OPT) 2026-08-28 - NO RULE HERE ANY MORE.
    It builds via the generic .CPP.OBJ suffix rule at the bottom of this file.
    The ONLY thing that ever kept it below the default was reason (1), the
    COMPILE-TIME grind (78+ minutes of EMULATED CPU in the LEVEL=3 dataflow
    passes).  That cost was an es40-emulator artefact; on real AlphaServer
    hardware it is minutes, and this file is the 3780-line BLITTER - the single
    hottest module in the KYRA image, and the direct cause of the laggy EOB2
    intro that the LEVEL=1 experiment was trying to fix.  Full optimization is
    the whole point of building on real hardware, so it gets it.
    IT HAS NEVER CRASHED THE BACK END AT LEVEL=3 - the assertion it used to hit
    belonged to wrapped_decodeFrameDeltaPage, which now lives in screen_delta.cpp
    (see the NOOPT/OPT1 entry for that file).  So this is not a re-run of the
    "precautionary /NOOPTIMIZE" mistake; there is no crash evidence to ignore.
    IF IT NOW GRINDS OR CRASHES: put back an explicit rule with $(CXXFLAGS_OPT1)
    (grind) or $(CXXFLAGS_NOOPT) (crash) and record the pass name here.
    The historical rationale for the LEVEL=1 rule is kept below for that purpose.

  screen.cpp - HISTORICAL (the removed LEVEL=1 rule), reason (1), the grind:
    History: it was at LEVEL=1 for that grind, then dropped to /NOOPTIMIZE when
    wrapped_decodeFrameDeltaPage hit a routine-codegen assertion.  THAT ROUTINE
    NO LONGER LIVES HERE - it was moved to screen_delta.cpp (see that entry in
    the NOOPT section), which is what actually fixed the assertion.  So the
    /NOOPTIMIZE lost its justification at that moment and survived only as a
    "precaution" - the exact anti-pattern the boxed warning in the NOOPT section
    forbids, and the one that cost this port seven rounds on VectorRendererSpec.

    WHY IT MATTERS NOW (performance, not a crash): screen.cpp is the 3780-line
    BLITTER.  At /NOOPTIMIZE its tight pointer-decode loops get no optimization
    at all, which under the es40 emulator showed up as a VERY LAGGY intro scene
    (continuous full-screen delta decoding) while ordinary gameplay - which
    redraws far less - stayed acceptable.

    LEVEL=1 was chosen over LEVEL=3 because LEVEL=3 is what ground for 78+
    minutes of emulated CPU on this very file.  LEVEL=1 kept basic optimization
    of the blit loops while skipping the LEVEL=3 dataflow passes that stalled.
    (That trade-off no longer applies - see the PROMOTED note above.)
  ------------------------- end historical note --------------------------

----------------------------------------------------------------------------
RETESTING THE PER-FILE RULES ON REAL HARDWARE
----------------------------------------------------------------------------
Eight files still deviate from $(OPT): five /NOOPTIMIZE
(surfacesdl-graphics, default-saves, config-manager, staticres, chargen), two
LEVEL=1 (screen_delta, cdtoons) and one /NOOPTIMIZE/NODEBUG/NOTRACEBACK
(winexe_pe).  EVERY ONE of them is a workaround for a REPRODUCIBLE HP C++ Alpha
back-end bug, so none of them was made obsolete by changing the build host.  But
a failed experiment now costs one compile instead of a wasted emulator evening,
so retesting them is finally cheap.  If you want to try:

  1. Pick ONE file.  Change ONE thing.  These crashes have historically been
     misdiagnosed by changing two variables at once.
  2. Order to try, per file: $(CXXFLAGS) first (plain full optimization), then
     $(CXXFLAGS_OPT1).  Do NOT go the other way round - the boxed warning in the
     NOOPT section applies: /NOOPTIMIZE is not the "safe" end of the scale, it has
     CAUSED a crash in this tree (GEM_CX_* passes run at every level and see the
     LARGEST live sets when nothing is optimized away).
  3. To retest one file:  DELETE its .OBJ, then MMK/IGNORE=WARNING.  That
     recompiles the one file and relinks; it does NOT trigger a full rebuild.
     Remember both libraries and both images depend on it - the relink is normal.
  4. Record the RESULT in the file's comment block either way.  A crash that
     recurs at LEVEL=4 is as valuable to write down as one that clears, and this
     file's history shows how expensive an unrecorded negative result is.

WORTH RETESTING FIRST (best payoff / most doubtful justification):
  [.engines.kyra]screen_delta.obj - the other half of the per-frame WSA decode
    pipeline (screen.cpp is now at full $(OPT), so this file is the remaining
    bottleneck in that path).  Its own comment already records that the
    /NOOPTIMIZE was applied in the SAME round as the TU split and was never
    tested independently of it.  Its comment warns against LEVEL=3 because
    GEM_CX_EXTEND_LIFETIME was one of the observed passes - that warning stands
    for LEVEL=4 too, so if you try it, try it knowing it may well crash.
  [.engines.kyra]staticres.obj - /NOOPTIMIZE costs nothing here (the file is
    nothing but static tables), so this one is NOT worth a round trip.  Listed
    only so nobody spends one on it.
----------------------------------------------------------------------------

----------------------------------------------------------------------------
Single suffix rule - compiles every .CPP into its .OBJ IN PLACE while the
default directory stays at this SRC root.  $(MMS$SOURCE) carries the
[.subdir] prefix, so [.engines.kyra]screen.cpp -> [.engines.kyra]screen.obj,
and the uniform /INCLUDE resolves both root-relative and same-directory
includes (see the header comment).
----------------------------------------------------------------------------
    .SUFFIXES
    .SUFFIXES .OBJ .CPP

    .CPP.OBJ :
    	$(CXX) $(CXXFLAGS) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)
