C:\RETRO\DOS\TP\TOOLCH~1>type turbop~3.htm
Turbo Pascal Toolchain, Part 3: Overlays, Memory Models, and Link Strategy
This article is rewritten to be explicitly source-grounded against the Turbo Pascal 5.0 Reference Guide (1989), Chapter 13 (“Overlays”) plus Appendix B directive entries.
Structure map. 1) Why overlays existed—mechanism, DOS memory pressure, design tradeoffs. 2) TP5 hard rules and directive semantics. 3) FAR/near call model and memory implications. 4) Build and link strategy for overlaid programs. 5) Runtime initialization: OvrInit, OvrInitEMS, OvrSetBuf usage and diagnostics. 6) Overlay buffer economics and memory budget math. 7) Failure triage and performance profiling mindset. 8) Migration from non-overlay projects. 9) Engineering checklist and boundary caveats.
Version note. This article is grounded in the TP5 Reference Guide. Borland Pascal 7 and later overlay implementations may differ in details (e.g. EMS handling, buffer API). The core rules—{$O+}, FAR chain, compile-to-disk, init-before-use—tend to hold across versions, but when in doubt, consult the manual for your specific toolchain. TP6/TP7 improvements are beyond the scope of this piece; the TP5 baseline remains the most widely documented and forms a stable reference.
Why overlays existed
In TP5 real-mode DOS workflows, overlays are a memory-management strategy: keep non-hot code out of always-resident memory and load it on demand. Conventional memory in DOS is capped at roughly 640 KB; TSRs, drivers, and stack/heap shrink the usable space. A large application can easily exceed that budget if all code is resident.
Mechanism. The overlay manager maintains a buffer in conventional memory.
Overlaid routines live in a separate .OVR file on disk. On first call into
an overlaid routine, the manager loads the appropriate block into the buffer
and transfers control. Subsequent calls to already-loaded overlays execute
in-place; no disk access. When the buffer fills and a new overlay must load,
the manager discards inactive overlays first (least-recently-used policy),
then loads the requested block.
Constraints. The buffer must hold at least the largest overlay (including fix-up data). Overlay call-path constraints matter: cross-calling overlay clusters—routines in overlay A calling routines in overlay B—force repeated swaps if the buffer is too small. Design the call graph so overlay entry points are used in bursts; avoid ping-pong patterns (A→B→A→B) where each transition evicts the previous overlay. Cold code that runs infrequently benefits most; hot paths that recur in tight loops should stay resident. A report generator that runs once per session is an ideal overlay candidate; a validation routine called on every keystroke is not.
Failure modes. Undersized buffer: visible thrashing, multi-hundred-millisecond stalls on each swap. Missing .OVR at runtime: init fails, calling overlaid code yields error 208. Incorrect FAR-call chain: corruption or crash when control returns through a near-call frame.
Design tradeoffs. Overlays reduce resident footprint at the cost of latency
on first use and complexity in build and deployment. They help when (a) total
code size exceeds available conventional memory, or (b) resident footprint
must shrink to coexist with TSRs or other programs. They hurt when cold code
is called frequently in alternation—e.g. A→B→A→B—because each transition may
force a reload. Packaging and deployment hazards: the .OVR file must
deploy alongside the .EXE with a matching base name. ZIP extracts that
place .EXE in one folder and .OVR in another, or installers that omit the
.OVR, produce ovrNotFound at startup. Document in release notes that
both files must stay together; test packaging on a clean directory.
TP5 hard rules (not optional style)
For TP5 overlaid programs, these are the baseline rules:
- Overlaid units must be compiled with
{$O+}. - At any call to an overlaid routine in another module, all active routines in the current call chain must use FAR call model.
- Use
{$O unitname}in the program (afteruses) to select overlaid units. usesmust listOverlaybefore overlaid units.- Programs with overlaid units must compile to disk (not memory).
TP5 also states that among the listed standard units only Dos is overlayable;
System, Overlay, Crt, Graph, Turbo3, and Graph3 are not.
Tuning workflow. Before enabling overlays, identify cold units (e.g. report
generators, rarely-used wizards) and compile them with {$O+}. Add {$O unitname}
one unit at a time and rebuild; verify .OVR appears and size changes as
expected. Link-map triage: with -Fm (or equivalent map-file option) the
linker produces a .MAP file. Overlaid segments appear in a dedicated overlay
region; resident segments stay in the main program listing. If you add
{$O UnitName} but the map shows that unit’s code still in the main program,
the directive did not take effect—often due to placement after uses or a
compile-to-memory build. If link fails or .OVR is missing, the overlay
selection is not taking effect—check directive placement and uses order.
Failure when rules are violated. Omitting {$O+} on an overlaid unit:
compiler error. Omitting {$F+} on a caller in the chain: link may succeed
but runtime can corrupt. Forgetting uses Overlay before overlaid units: the
Overlay unit’s runtime is not linked; overlay manager never initializes.
Compiling to memory: overlay linker path is bypassed; no .OVR produced.
What {$O+} actually changes
{$O+} is not just a marker. TP5 documents concrete codegen precautions:
when calls cross units compiled with {$O+} and string/set constants are passed,
the compiler copies code-segment-based constants into stack temporaries before
passing pointers. This prevents invalid pointers if overlay swaps replace caller
unit code areas.
That detail is the reason “works in tiny test, crashes in integrated flow” happens when overlay directives are inconsistent.
Mechanism. Without {$O+}, a call like DoReport('Monthly') may pass a
pointer to a constant in the code segment. If DoReport is overlaid and triggers
a swap, the caller’s code segment can be evicted; the pointer then points at
overlay buffer contents, not the original string. With {$O+}, the compiler
emits logic to copy the constant to the stack and pass that address instead.
Constraint. {$O unitname} has no effect inside a unit—it is a program-level
directive. The unit must already be compiled with {$O+} or the compiler
reports an error. Mixing {$O+} and {$O-} inconsistently across a call
chain is a common source of intermittent corruption. The same rule applies
to sets passed by reference: set constants in the code segment can become
invalid if the caller is evicted during an overlay swap. TP5 copies both
strings and sets into stack temporaries when the callee may be overlaid.
Example of the constant-copy hazard. In a unit compiled without {$O+},
WriteReport(HeaderText) might pass the address of HeaderText as stored
in the code segment. If WriteReport is overlaid and triggers a swap, the
caller’s code may be evicted; the callee then reads from wrong memory. With
{$O+}, the compiler generates a copy to a stack temporary and passes that
address—safe regardless of overlay activity.
FAR-call requirement explained operationally
Manual example pattern: MainC -> MainB -> OvrA where OvrA is in an overlaid
unit. At call to OvrA, both MainB and MainC are active, so they must use
FAR model too.
Practical TP5-safe strategy:
{$O+,F+}in overlaid units{$F+}in main program and other units
TP5 notes the cost is usually limited: one extra stack word per active routine and one extra byte per call.
FAR vs near implications. Near calls use a 2-byte return address (offset
only); FAR calls use 4 bytes (segment:offset). Each active frame on the stack
therefore costs one extra word (2 bytes) with {$F+}. For deeply nested call
chains—e.g. main → menu → dialog → validator → report—the stack growth is
2 * depth bytes. In a 64 KB stack, that is rarely the bottleneck; the overlay
buffer and heap compete more for conventional memory.
Memory budget math. A rough breakdown for a typical overlaid TP5 app:
- DOS + drivers + TSRs: ~100–200 KB (varies)
- Resident code (main, Crt, Graph init, hot units): ~80–150 KB
- Overlay buffer (
OvrSetBuf): 32–64 KB typical, up toMemAvail + OvrGetBuf - Heap (
{$M min,max}): remaining conventional memory - Stack: usually 16–32 KB
If MemAvail at startup is small, increasing overlay buffer via OvrSetBuf
reduces heap. Tune with MemAvail and OvrGetBuf diagnostics before and
after OvrSetBuf. Runtime initialization variants: OvrSetBuf must run
while the heap is empty. Two common orderings: (a) OvrInit → OvrSetBuf →
heap consumers (Graph, etc.); or (b) OvrInit only, accepting the default
buffer. If your program uses Graph, call OvrSetBuf before InitGraph—the
Graph unit allocates large video and font buffers from the heap, which locks
in the overlay buffer size. Late OvrSetBuf after any heap allocation has
no effect; no runtime error, but the buffer stays at its initial minimum.
Segment implications. In real-mode 8086, a FAR call pushes segment and offset; a near call pushes only offset. When resident code calls overlay code, control crosses segment boundaries. The overlay buffer lives in a different segment than the main code segment. A near return in the caller would pop only 2 bytes—the offset—and jump back with a stale segment, typically causing an immediate crash or wild jump. FAR ensures the full return address is preserved. This is why the rule applies to the entire call chain, not just the immediate caller.
Build and selection flow (TP5)
Minimal structure:
program App;
{$F+}
uses Overlay, Dos, MyColdUnit, MyHotUnit;
{$O MyColdUnit}Key nuances:
{$O unitname}has no effect inside a unit.- It only selects used program units for placement in
.OVR. - Unit must already be compiled in
{$O+}state or compiler errors.
Build/link strategy. Overlays are a link-time feature. The pipeline:
- Compile each unit to
.TPU(with correct{$O+}for overlaid units). - Compile the main program; the compiler records overlay directives.
- Link produces
.EXEand.OVR. The linker segregates code marked for overlay into the.OVRfile and emits call stubs in the.EXE.
A minimal batch build for an overlaid project:
|
|
Checklist. After a clean build: (1) .EXE and .OVR exist; (2) .OVR
size roughly matches sum of overlaid unit contributions; (3) running without
.OVR fails explicitly at init, not later with corruption; (4) if using
external .OBJ modules that participate in overlay call chains, ensure they
use FAR call/return conventions compatible with TP’s expectations; (5) for
release builds, confirm both artifacts are present in the output directory
and in any packaging script—CI or automated build pipelines that copy only
.EXE will ship a broken product.
IDE vs CLI parity. Overlay options in the IDE (Compiler → Overlay unit
names, Memory compilation off) must match what a batch build does. If the
IDE build produces .OVR but the CLI build does not, the IDE may have
overlay settings that are not reflected in project files. Document the
exact options and replicate them in the batch script.
Using .MAP for overlay forensics. With link map output enabled (e.g.
tpc -Fm or IDE Linker → Map file), the map file shows segment addresses and
symbol placement. Overlaid segments appear in the overlay region; resident
segments in the main program. Link-map-based triage: (1) Compare map before
and after adding {$O unitname}—overlaid units should move from main-program
segments into the overlay section. (2) If a unit’s code remains in the main
program despite {$O unitname}, the directive was ignored (check placement,
compile-to-disk, uses order). (3) Use segment sizes in the map to estimate
.OVR size and the minimum OvrSetBuf; the largest overlay block sets the
floor. Comparing map before and after adding {$O unitname} confirms which
code moved to the overlay file.
Runtime initialization contract
Overlay manager must be initialized before first overlaid call:
OvrInit('APP.OVR');
if OvrResult <> ovrOk then Halt(1);If initialization fails and you still call overlaid code, TP5 behavior is runtime error 208 (“Overlay manager not installed”).
OvrInit behavior (TP5)
- Opens/initializes overlay file.
- If filename has no path, search includes current directory, EXE directory
(DOS 3.x), and
PATH. - Typical errors:
ovrError,ovrNotFound.
OvrInitEMS behavior (TP5)
- Attempts to load overlay file into EMS.
- On success, subsequent loads become in-memory transfers from EMS to the overlay buffer—faster than disk, but overlays still execute from conventional memory. EMS acts as a paging store, not execution space.
- On error, manager keeps functioning with disk-backed overlay loading.
EMS usage pattern. Call OvrInit first, then OvrInitEMS. If OvrResult
is ovrOk after OvrInitEMS, the manager uses EMS for overlay storage. On
ovrNoEMSDriver or ovrNoEMSMemory, the program continues with disk loading;
no need to fail. EMS reduces load latency on machines with expanded memory
but is optional for correctness. EMS tradeoffs: EMS removes disk I/O from
overlay loads—a floppy or slow hard disk can add 100–500 ms per swap; EMS
cuts that to a few milliseconds. The tradeoff is memory pressure: the full
.OVR is duplicated in EMS. On a machine with limited EMS (e.g. 256 KB),
loading a 120 KB overlay file may exhaust EMS and force fallback to disk
anyway. Check OvrResult after OvrInitEMS; if it is ovrNoEMSMemory,
consider reducing overlay count or advising users with low EMS to free
expanded memory. On machines without EMS, OvrInitEMS returns ovrNoEMSDriver
and the program silently continues with disk—no special handling required.
OvrResult semantics
Unlike IOResult, TP5 documents that OvrResult is not auto-cleared when
read. You can inspect it directly without first copying.
Usage patterns and diagnostics
Pattern 1: minimal init with explicit path. Avoid search-order surprises by building the overlay path from the executable location:
procedure InitOverlays;
var ExeDir, ExeName, ExeExt: PathStr;
begin
FSplit(ParamStr(0), ExeDir, ExeName, ExeExt);
OvrInit(ExeDir + ExeName + '.OVR');
if OvrResult <> ovrOk then
begin
case OvrResult of
ovrError: WriteLn('Overlay format error or program has no overlays');
ovrNotFound: WriteLn('Overlay file not found: ', ExeDir + ExeName + '.OVR');
else WriteLn('OvrResult=', OvrResult);
end;
Halt(1);
end;
end;Pattern 2: EMS-optional with fallback. Try EMS first; if it fails, disk loading still works:
OvrInit(ExeDir + ExeName + '.OVR');
if OvrResult <> ovrOk then Halt(1);
OvrInitEMS; { ignore result: disk loading remains available }Pattern 3: buffer tuning before heap allocation. Call OvrSetBuf while the
heap is empty. With Graph unit:
OvrInit(OvrFile);
if OvrResult <> ovrOk then Halt(1);
OvrSetBuf(50000); { before InitGraph }
InitGraph(...); { Graph allocates from heap }OvrResult reference (TP5 manual-confirmed): ovrOk, ovrError,
ovrNotFound, ovrIOError, ovrNoEMSDriver, ovrNoEMSMemory.
OvrSetBuf diagnostics. The call can fail if the heap is not empty or
BufSize is out of range. TP5 does not document a dedicated OvrResult
for OvrSetBuf failure; practical approach: call OvrSetBuf(DesiredSize)
early, then check OvrGetBuf to see if the buffer actually increased. If
OvrGetBuf stays at the initial size, the request was rejected (heap in
use or size constraint). Add a diagnostic mode that prints MemAvail,
OvrGetBuf, and MaxAvail at startup to support troubleshooting.
Initialization ordering variants. Three common patterns: (a) Minimal:
OvrInit(path) only, accept default buffer—works when overlays are small
and rarely cross-call. (b) Buffer-tuned: OvrInit → OvrSetBuf(n) before
any heap use—required when Graph or other heap consumers follow. (c)
EMS-aware: OvrInit → OvrInitEMS → OvrSetBuf—EMS can speed loads,
but OvrSetBuf still controls conventional-memory buffer size. In all cases,
init must complete before the first overlaid call; unit initializations that
invoke overlaid code will fail with error 208.
How the Overlay unit lays out memory (brief)
TP5 splits resident and overlaid code at artifact level:
.EXE: resident (non-overlaid) program parts.OVR: overlaid units selected by{$O unitname}
At runtime, overlaid code executes from a dedicated overlay buffer in conventional memory. Manual-confirmed points:
- initial buffer size is the smallest workable value: the largest overlay (including fix-up information)
OvrSetBufchanges buffer size by taking/releasing heap spaceOvrSetBufrequires an empty heap to take effect- manager tries to keep as many overlays resident as possible and discards inactive overlays first when space is needed
- with EMS (
OvrInitEMS), overlays are still copied into normal memory buffer before execution
Linker behavior (manual-confirmed). The TP5 overlay linker produces one
.OVR per executable. All units marked with {$O unitname} contribute
code to that file. The linker decides the layout; you do not control which
routines share overlay blocks. Unused routines in overlaid units may be
omitted (dead-code elimination). The .OVR is loaded as a whole or in
logical chunks depending on the manager implementation—TP5 docs do not
specify the exact block structure, but the runtime behavior (LRU discard,
buffer sizing) is documented. When sizing the overlay buffer, use the
largest single overlay block; the linker may pack multiple small routines
into one loadable block, so OvrGetBuf after init reflects the runtime’s
minimum—the size of the largest block the manager must load in one swap.
FAR/near and overlay placement. Overlaid code runs in a separate buffer;
the linker emits FAR calls to reach it from resident code. Resident routines
that call overlaid routines must use FAR so the return address correctly
restores the caller’s segment. Near calls in that chain would leave a truncated
return address and corrupt the stack. The constraint applies to the entire
active call chain at the moment of the overlaid call: main → menu → dialog →
validator → report. If report is overlaid, every routine in that path must
use FAR. A single near caller in the chain—e.g. a quick helper compiled with
{$F-}—can cause intermittent crashes when control returns through that
frame; the stack ends up with a mismatched segment.
Buffer economics: OvrGetBuf and OvrSetBuf
TP5 starts with a minimal buffer sized to the largest overlay (including fix-up data). For cross-calling overlay clusters, this can thrash badly.
OvrSetBuf tunes buffer size, with constraints:
BufSizemust be >= initial sizeBufSizemust be <=MemAvail + OvrGetBuf- heap must be empty, otherwise call returns error/has no effect
Important ordering rule: if Graph is used, call OvrSetBuf before
InitGraph because Graph allocates heap memory.
Tuning workflow. (1) At startup, log MemAvail and OvrGetBuf before any
OvrSetBuf. (2) Run a representative workload (menu navigation, report run,
etc.) and note perceived stalls. (3) Increase buffer in steps (e.g. 16K → 32K
→ 48K → 64K) and re-test. (4) Stop when stalls disappear or MemAvail drops
unsafely. (5) Adjust {$M min,max} if the larger buffer causes heap
shortage during normal operation.
Practical overlay tuning checklist:
| Step | Action | Success criteria |
|---|---|---|
| 1 | OvrGetBuf after init |
Know baseline buffer size |
| 2 | Run cold-path sequence 3× | Count noticeable pauses |
| 3 | OvrSetBuf(2 * OvrGetBuf) |
Fewer pauses |
| 4 | Iterate until smooth or MemAvail < 20K |
Balanced |
Concrete sizing examples. If the largest overlay is 24 KB, the initial
buffer is ~24 KB. With two overlays that cross-call (e.g. Report → Chart),
a 24 KB buffer forces a swap on every transition. OvrSetBuf(48000) holds
both; transitions become in-memory. If MemAvail at startup is 120 KB,
reserving 48 KB for overlays leaves ~72 KB for heap—adequate for many apps.
If MemAvail is 40 KB, a 48 KB buffer request may fail or leave almost no
heap; tune down or reduce resident code.
Buffer and Graph/BGI interaction. The Graph unit allocates video buffers,
font caches, and driver data from the heap at InitGraph time. If you call
OvrSetBuf after InitGraph, the heap is no longer empty; the call has no
effect and the buffer stays at its initial size. Always initialize overlays
and set buffer size before any substantial heap allocation. Order: OvrInit
→ OvrSetBuf → InitGraph (or other heap consumers). See Part 4: BGI
integration
for graphics-specific overlay notes.
Failure triage and performance profiling mindset
Symptom → check → fix:
-
Link error / unresolved overlay symbol: Unit not in overlay selection, or mixed far/near in external
.OBJ. Verify{$O unitname}and{$F+}on all units in the call chain. -
Error 208 at runtime: Overlay manager not installed. Either
OvrInitwas never called, or it failed and execution continued. Add init check before any overlaid call. -
ovrNotFoundat startup: Path wrong. UseFSplit(ParamStr(0), ...)to build overlay path from EXE location; avoid relying on current directory. -
ovrErrorat startup:.OVRdoes not match.EXE(rebuilt one but not the other), or program has no overlays. Clean rebuild both, verify.OVRexists. -
Intermittent slowdown / visible stalls: Buffer thrashing. Profile by repeating the slow action and measuring; increase
OvrSetBufor move hot helpers to resident units. Cross-reference with the link map: if the buffer is smaller than the sum of frequently-used overlay block sizes, thrashing is expected. Increase buffer until it holds the active set, or consolidate overlays to reduce cross-calling.
Performance profiling mindset. Overlay cost is load time, not execution
time. A loaded overlay runs at full speed. Latency profiling workflow:
(1) isolate the user action that triggers the stall; (2) wrap the suspect
call in GetTime/GetMsCount timing; (3) run the action multiple times—first
call (cold) vs later calls (warm); (4) if cold is 100+ ms and warm is under 5 ms,
the stall is overlay load; (5) trace the call path to see which overlaid units
participate; (6) either enlarge buffer (to hold multiple overlays) or move
frequently-alternating code to resident units. Simple timing around
suspect calls (GetTime before/after) confirms whether the stall aligns with
overlay load. Minimal diagnostic snippet:
var Hour, Min, Sec, Sec100: Word;
StartTotal, EndTotal: LongInt;
begin
GetTime(Hour, Min, Sec, Sec100);
StartTotal := LongInt(Sec)*100 + Sec100;
RunSuspectedOverlaidRoutine;
GetTime(Hour, Min, Sec, Sec100);
EndTotal := LongInt(Sec)*100 + Sec100;
WriteLn('Elapsed: ', EndTotal - StartTotal, ' centiseconds');
end;If the first call shows hundreds of centiseconds and later calls are near zero, the overlay load is the bottleneck. Disk-based loads on a 360K floppy can reach 500 ms or more; EMS typically drops that to under 20 ms. Use this to correlate user-reported “slow menu” complaints with overlay activity.
LRU behavior in practice. The overlay manager keeps the most recently used overlays in the buffer. Alternating rapidly between overlay A and overlay B with a buffer that holds only one forces a load on every switch. Holding both in buffer (or reducing cross-calls) eliminates that cost. Profile the actual call sequence during representative use; if the user typically runs Report then Chart then Report again, a buffer large enough for both pays off.
Migration from non-overlay projects
Converting a working non-overlaid program to use overlays:
-
Identify cold units. Report generators, rarely-used dialogs, optional modules. Do not overlay hot loops (main menu, render loop, I/O). Practical heuristic: if a routine runs on every frame or in a tight loop, keep it resident. If it runs only when the user selects a specific menu item or triggers an infrequent action, it is a cold-path candidate. Use profiler or manual instrumentation if unsure.
-
Add
{$O+}and{$F+}to candidate units. Add{$F+}to main program and any unit that calls overlaid code (directly or transitively). -
Add
uses Overlayas first unit in the main program. Add{$O UnitName}
for each cold unit, one at a time. -
Enable compile-to-disk if building in IDE (Options → Compiler → Directories or equivalent).
-
Add init block before first overlaid call. Use
FSplit+OvrInit+OvrResultcheck. -
Clean rebuild. Verify
.EXEand.OVRboth produced. Run missing-OVR test. Run overlay-thrash test and tuneOvrSetBuf. -
Regression test the full feature set. Overlays change memory layout; subtle bugs (e.g. uninitialized pointers, stack overflow) can surface.
Rollback: Remove uses Overlay, {$O unitname}, and {$O+} from overlaid
units; reduce {$F+} if no longer needed. Rebuild; .OVR will not be produced,
all code returns to .EXE.
Incremental migration. Do not overlay everything at once. Start with one clearly cold unit. Validate build, init, and runtime. Add a second; re-validate. If a new overlay causes problems, the failure is localized to that unit or its callers. Batch migration makes triage much harder.
Common migration pitfalls. (a) Overlaying a unit that is used by many
others—transitive callers all need {$F+}. (b) Forgetting {$O+} on one
unit in a cluster—inconsistent codegen can cause pointer corruption. (c)
Deploying .EXE without .OVR—build and packaging scripts must include both.
(d) Calling overlaid code before OvrInit—e.g. from unit initialization
sections—crashes; init must run in the main program before any overlaid
routine is invoked. (e) Packaging hazards: self-extracting archives that
copy only .EXE files, installers with file filters that exclude .OVR, or
ZIP-based distributions where users extract to different folders—all produce
ovrNotFound. Include both files in every distribution artifact; add a
post-install check that verifies EXE_dir + base_name + '.OVR' exists, or
document clearly that the program requires both files in the same directory.
What is manual-confirmed vs inferred
Manual-confirmed in TP5:
- directive rules (
$O+,$O unitname,$F+guidance) - compile-to-disk requirement
- runtime API behavior (
OvrInit,OvrInitEMS,OvrSetBuf,OvrResult) - FAR-chain safety requirement and consequences
Intentionally not claimed here as fixed TP5 public spec:
- detailed byte-level
.OVRfile format guarantees - universal behavior across TP6/TP7/BP7 variants without version checks
Those may be explored, but should be treated as version-scoped reverse engineering.
Engineering checklist
Before shipping an overlaid TP5 build:
- verify overlaid units compiled with
{$O+} - verify FAR-call policy (
{$F+}strategy) across active-call paths - verify
{$O unitname}directives anduses Overlayordering - verify
.EXEand.OVRartifact pair in package - run one missing-OVR startup test and confirm controlled failure path
- run one overlay-thrash workload and tune with
OvrSetBuf - log
MemAvailandOvrGetBufat startup for support diagnostics - document
OvrSetBufvalue and{$M}in build notes - include
.OVRin installer and distribution package; document that.EXEand.OVRmust stay together
Deployment note. End users rarely see .OVR files. Installer scripts
and ZIP distributions must include both .EXE and .OVR with matching
base names. A self-extracting archive or installer that only grabs the
.EXE will produce a program that fails at startup with ovrNotFound.
Packaging/deployment hazards: (1) Build scripts that copy *.EXE but not
*.OVR into a release directory. (2) Version-control or backup systems that
ignore *.OVR by default. (3) Users running from a network drive where the
.OVR lives on a different path than the .EXE. (4) Multi-directory
installs (e.g. EXE in \bin, OVR in \data) without updating the overlay
search path—OvrInit with no path uses current directory and EXE directory;
explicit path construction via ParamStr(0) avoids ambiguity. Add a
pre-release checklist item: verify both artifacts exist in the shipped package.