# DESCRIP.MMS - Standalone Kyra engine (ScummVM 1.8.1) build 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 : KYRA.EXE - a no-launcher, no-sound, fixed-640x480 loader that
#          auto-detects the game in the CURRENT (default) directory and runs it,
#          exactly like the FreeSCI / Sarien OpenVMS loaders.
#
# Engines compiled in: Kyra (LoK/HoF/MR) + LoL + EOB/EOB2 only.
# No audio device is ever opened (NullSdlMixerManager). Links only SDL 1.2 + X11.
#
# ----------------------------------------------------------------------------
# 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 "kyra/screen.h"        (engine sources; resolved via [.engines])
#   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 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 358 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.
#   KYRA_STANDALONE      -> our no-launcher / no-sound / fixed-window mode
#   DECLSPEC= , SDLCALL= -> neutralise Win32-isms in the SDL 1.2 headers (FreeSCI)
#   NO_SOUND             -> belt-and-braces; standalone build already mutes audio
DEFS = HAVE_CONFIG_H,SDL_BACKEND,POSIX,-
"ENABLE_KYRA=STATIC_PLUGIN",ENABLE_LOL,ENABLE_EOB,-
KYRA_STANDALONE,"DECLSPEC=","SDLCALL=",NO_SOUND

INCS = ([],SDL)

# /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-
/OPTIMIZE=(LEVEL=3,TUNE=HOST)/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 at /OPTIMIZE=LEVEL=3) - 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.
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").
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))

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 (358) - grouped by top-level module
# ----------------------------------------------------------------------------

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_OBJS = [.base]commandLine.obj,[.base]main.obj,[.base]plugins.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

ENGINES_OBJS = [.engines]advancedDetector.obj,[.engines]dialogs.obj,[.engines]engine.obj,-
[.engines]game.obj,[.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]eobcommon.obj,[.engines.kyra]eob.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_adlib.obj,[.engines.kyra]sound_amiga.obj,-
[.engines.kyra]sound.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,[.engines]obsolete.obj,[.engines]savestate.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

ALL_OBJS = $(AUDIO_OBJS),-
$(BACKENDS_OBJS),-
$(BASE_OBJS),-
$(COMMON_OBJS),-
$(ENGINES_OBJS),-
$(GRAPHICS_OBJS),-
$(GUI_OBJS),-
$(IMAGE_OBJS),-
$(VIDEO_OBJS)

# ----------------------------------------------------------------------------
# Object-list chunks for the object library (kyra_objs.olb).
# 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 written on their own line into
# the .opt file; LINK treats a newline in an options file as an object-spec
# separator (like a comma).  Chunks are split on group boundaries and verified
# to reproduce $(ALL_OBJS) exactly.  Keep in sync if adding files.
#
# 352 objects here, not 358.  SIX objects are deliberately absent and are linked
# directly on the LINK command line instead (see the kyra.exe rule):
#   posix-main.obj                                  - carries the transfer address
#   [.gui]error / debugger / saveload,
#   [.common]quicktime, [.engines.kyra]util         - 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.  This tree has
# five such pairs (common+gui error, audio+common quicktime, gui+kyra debugger,
# gui+kyra saveload, common+kyra util), 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 the
# tree, give it the same treatment.  These five are the ONLY collisions in the
# tree - verified by comparing the 358 basenames in $(ALL_OBJS); ThemeEval and
# scale2x are NOT among them, which is why they now live in the chunks below.
#
# 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]plugins.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.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]eobcommon.obj,[.engines.kyra]eob.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
O_ENGINES_2 = [.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
O_ENGINES_3 = [.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
O_ENGINES_4 = [.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_adlib.obj,[.engines.kyra]sound_amiga.obj,[.engines.kyra]sound.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
O_ENGINES_5 = [.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]vqa.obj,[.engines.kyra]wsamovie.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
O_GUI_2 = [.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

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

kyra.exe : $(ALL_OBJS) kyra_objs.olb 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,-
	[.gui]error.obj,-
	[.gui]debugger.obj,-
	[.gui]saveload.obj,-
	[.common]quicktime.obj,-
	[.engines.kyra]util.obj,-
	[]kyra_objs.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 full object list (358 files) is packaged into an object LIBRARY 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 kyra_objs.olb into the same cluster.  This is exactly
# how ScummVM links statically elsewhere; plugins.obj holds a hard reference to
# the Kyra registrator (g_KYRA_getObject / g_KYRA_type), so the librarian
# force-extracts the engine - it is NOT silently dropped.  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 358 C++ modules: with ~217 modules inserted (part way through
# $(O_ENGINES_3)) LIBRARIAN died with
#   %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 the 358 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 KYRA_OBJS.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 358
# 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 in $(O_ENGINES_3) 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 KYRA_OBJS_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.
kyra_objs.olb : $(ALL_OBJS)
	@ write sys$output "Building object library KYRA_OBJS.OLB ..."
	- delete []kyra_objs_tmp.olb;*
	library /create=(blocks:4000,modules:1000,globals:60000) /object kyra_objs_tmp.olb
	library /insert kyra_objs_tmp.olb $(O_AUDIO_1)
	library /insert kyra_objs_tmp.olb $(O_AUDIO_2)
	library /insert kyra_objs_tmp.olb $(O_AUDIO_3)
	library /insert kyra_objs_tmp.olb $(O_BACKENDS_1)
	library /insert kyra_objs_tmp.olb $(O_BACKENDS_2)
	library /insert kyra_objs_tmp.olb $(O_BASE_1)
	library /insert kyra_objs_tmp.olb $(O_COMMON_1)
	library /insert kyra_objs_tmp.olb $(O_COMMON_2)
	library /insert kyra_objs_tmp.olb $(O_ENGINES_1)
	library /insert kyra_objs_tmp.olb $(O_ENGINES_2)
	library /insert kyra_objs_tmp.olb $(O_ENGINES_3)
	library /insert kyra_objs_tmp.olb $(O_ENGINES_4)
	library /insert kyra_objs_tmp.olb $(O_ENGINES_5)
	library /insert kyra_objs_tmp.olb $(O_GRAPHICS_1)
	library /insert kyra_objs_tmp.olb $(O_GRAPHICS_2)
	library /insert kyra_objs_tmp.olb $(O_GUI_1)
	library /insert kyra_objs_tmp.olb $(O_GUI_2)
	library /insert kyra_objs_tmp.olb $(O_IMAGE_1)
	library /insert kyra_objs_tmp.olb $(O_VIDEO_1)
	@ write sys$output "Library built OK - promoting to KYRA_OBJS.OLB ..."
	- delete []kyra_objs.olb;*
	rename []kyra_objs_tmp.olb []kyra_objs.olb
	@ write sys$output "KYRA_OBJS.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 now comes to ~912 chars ($(O_ENGINES_2)/$(O_AUDIO_1) at 699
# plus $(GLOBAL_HDRS) at 210, after memory.h/algorithm.h/array.h were added on
# 2026-07-26), 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 all 358
# objects exactly once (posix-main.obj is the one exception - it is not in any
# chunk, so it gets its own line below).  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 358 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.
# ADD IT the next time a full rebuild happens for some other reason - it is
# essentially free at that point.  Until then, treat any edit to sdl-sys.h as
# REQUIRING a manual `MMK CLEAN`.
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

$(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)
$(O_COMMON_1) : $(GLOBAL_HDRS)
$(O_COMMON_2) : $(GLOBAL_HDRS)
$(O_ENGINES_1) : $(GLOBAL_HDRS)
$(O_ENGINES_2) : $(GLOBAL_HDRS)
$(O_ENGINES_3) : $(GLOBAL_HDRS)
$(O_ENGINES_4) : $(GLOBAL_HDRS)
$(O_ENGINES_5) : $(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)

# 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

clean :
	@ write sys$output "Cleaning objects ..."
	- delete [...]*.obj;*
	- delete []x11_link.opt;*
	- delete []kyra_objs.olb;*
	- delete []kyra.map;*
	- delete []kyra.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.  LEVEL=3-only -> /NOOPTIMIZE (or
#                             LEVEL=1).  The original class this section was for.
#   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 object #358 (a new port-specific file).
#
# 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 /OPTIMIZE=(LEVEL=3,TUNE=HOST), which is where it belongs.  Nothing about the
# SOURCE was ever wrong.  Kept here as the cautionary entry for this whole section.
#
# 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 : RETURNED HERE 2026-07-29, reason (1) - the LEVEL=3 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, NOT LEVEL=3, AND THAT IS DELIBERATE: LEVEL=3 is what ground for 78+
#     minutes of emulated CPU on this very file.  LEVEL=1 keeps basic optimization
#     of the blit loops while skipping the LEVEL=3 dataflow passes that stall.
#     If the intro is still slow after this, the next step is NOT to try LEVEL=3
#     on the whole file - it is to profile which routine dominates.
#
#     IF THIS REGRESSES: a codegen crash here means the split left something
#     behind; revert to $(CXXFLAGS_NOOPT) and record the traceback next to it.
[.engines.kyra]screen.obj : -
[.engines.kyra]screen.cpp
	$(CXX) $(CXXFLAGS_OPT1) /OBJECT=$(MMS$TARGET) $(MMS$SOURCE)

# ----------------------------------------------------------------------------
# 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)
