<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Overlays on TurboVision</title>
    <link>https://turbovision.in6-addr.net/tags/overlays/</link>
    <description>Recent content in Overlays on TurboVision</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Tue, 21 Apr 2026 14:06:12 +0000</lastBuildDate>
    <atom:link href="https://turbovision.in6-addr.net/tags/overlays/index.xml" rel="self" type="application/rss&#43;xml" />
    
    
    
    <item>
      <title>Turbo Pascal Overlay Tutorial: Build, Package, and Debug an OVR Application</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-overlay-tutorial-build-package-and-debug-an-ovr-application/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Mon, 09 Mar 2026 09:46:27 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-overlay-tutorial-build-package-and-debug-an-ovr-application/</guid>
      <description>&lt;p&gt;This tutorial is intentionally practical. You will build a small Turbo Pascal
program with one resident path and one overlayed path, then test deployment and
failure behavior.&lt;/p&gt;
&lt;p&gt;If your install names/options differ, keep the process and adapt the exact menu
or command names.&lt;/p&gt;
&lt;h2 id=&#34;goal-and-expected-outcomes&#34;&gt;Goal and expected outcomes&lt;/h2&gt;
&lt;p&gt;Goal: move a cold code path out of always-resident memory and verify it loads
on demand from &lt;code&gt;.OVR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Expected outcomes before you start:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;build output includes both &lt;code&gt;.EXE&lt;/code&gt; and &lt;code&gt;.OVR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;startup succeeds only when overlay initialization succeeds&lt;/li&gt;
&lt;li&gt;cold feature call has first-hit latency and warm-hit improvement&lt;/li&gt;
&lt;li&gt;removing &lt;code&gt;.OVR&lt;/code&gt; produces controlled error path, not random crash&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;minimal-project-layout&#34;&gt;Minimal project layout&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OVRDEMO/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  MAIN.PAS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  REPORTS.PAS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  BUILD.BAT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;step-1-write-resident-core-and-cold-module&#34;&gt;Step 1: write resident core and cold module&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;REPORTS.PAS&lt;/code&gt; (cold path candidate):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{$O+}  { TP5 requirement: unit may be overlaid }
{$F+}  { TP5 requirement for safe calls in overlaid programs }
unit Reports;

interface
procedure RunMonthlyReport;

implementation

procedure RunMonthlyReport;
var
  I: Integer;
  S: LongInt;
begin
  S := 0;
  for I := 1 to 25000 do
    S := S + I;
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;MAIN.PAS&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program OvrDemo;
{$F+}  { TP5: use FAR call model in non-overlaid code as well }
{$O+}  { keep overlay directives enabled in this module }

uses
  Overlay, Crt, Dos, Reports;
{$O Reports}  { select this used unit for overlay linking }

var
  Ch: Char;
  ExeDir, ExeName, ExeExt: PathStr;
  OvrFile: PathStr;

procedure InitOverlays;
begin
  FSplit(ParamStr(0), ExeDir, ExeName, ExeExt);
  OvrFile := ExeDir + ExeName + &amp;#39;.OVR&amp;#39;;
  OvrInit(OvrFile);
  if OvrResult &amp;lt;&amp;gt; ovrOk then
  begin
    Writeln(&amp;#39;Overlay init failed for &amp;#39;, OvrFile, &amp;#39;, code=&amp;#39;, OvrResult);
    Halt(1);
  end;
  OvrSetBuf(60000);
end;

begin
  InitOverlays;
  Writeln(&amp;#39;Press R to run report, ESC to exit&amp;#39;);
  repeat
    Ch := ReadKey;
    case UpCase(Ch) of
      &amp;#39;R&amp;#39;:
        begin
          Writeln(&amp;#39;Running report...&amp;#39;);
          RunMonthlyReport;
          Writeln(&amp;#39;Done.&amp;#39;);
        end;
    end;
  until Ch = #27;
end.&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;step-2-enable-overlay-policy&#34;&gt;Step 2: enable overlay policy&lt;/h2&gt;
&lt;p&gt;Overlay output is not triggered by &lt;code&gt;uses Overlay&lt;/code&gt; alone. You need both:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;mark unit as overlay-eligible at compile time&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;select unit for overlaying from the main program&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For &lt;strong&gt;Turbo Pascal 5.0&lt;/strong&gt; (per Reference Guide), these are hard rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;all overlaid units must be compiled with &lt;code&gt;{$O+}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;active call chain must use FAR call model in overlaid programs&lt;/li&gt;
&lt;li&gt;practical safe pattern: &lt;code&gt;{$O+,F+}&lt;/code&gt; in overlaid units, &lt;code&gt;{$F+}&lt;/code&gt; in other units and main&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{$O UnitName}&lt;/code&gt; must appear after &lt;code&gt;uses&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uses&lt;/code&gt; must name &lt;code&gt;Overlay&lt;/code&gt; before any overlaid unit&lt;/li&gt;
&lt;li&gt;build must be to disk (not memory)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The full &lt;code&gt;REPORTS.PAS&lt;/code&gt; and &lt;code&gt;MAIN.PAS&lt;/code&gt; examples above include these directives
directly.&lt;/p&gt;
&lt;h3 id=&#34;why-o-exists-tp5-technical-reason&#34;&gt;Why &lt;code&gt;{$O+}&lt;/code&gt; exists (TP5 technical reason)&lt;/h3&gt;
&lt;p&gt;In TP5, &lt;code&gt;{$O+}&lt;/code&gt; is not just a &amp;ldquo;permission bit&amp;rdquo; for overlaying. It also changes
code generation for calls between overlaid units to keep parameter pointers safe.&lt;/p&gt;
&lt;p&gt;Classic hazard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;caller unit passes pointer to a code-segment-based constant (for example a
string/set constant)&lt;/li&gt;
&lt;li&gt;callee is in another overlaid unit&lt;/li&gt;
&lt;li&gt;overlay swap can overwrite caller code segment region&lt;/li&gt;
&lt;li&gt;raw pointer becomes invalid&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TP5 &lt;code&gt;{$O+}&lt;/code&gt;-aware code generation mitigates this by copying such constants into
stack temporaries before passing pointers in overlaid-to-overlaid scenarios.&lt;/p&gt;
&lt;p&gt;Typical source-level shape:&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;REPORTS.PAS&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{$O+}  { TP5 mandatory for overlaid units }
{$F+}  { TP5 FAR-call requirement }
unit Reports;
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In &lt;code&gt;MAIN.PAS&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program OvrDemo;
uses Overlay, Crt, Dos, Reports;
{$O Reports}  { overlay unit-name directive: mark Reports for overlay link }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Without the unit-name selection (&lt;code&gt;{$O Reports}&lt;/code&gt; or equivalent IDE setting), the
unit can stay fully linked into the EXE even if &lt;code&gt;{$O+}&lt;/code&gt; is present.&lt;/p&gt;
&lt;p&gt;TP5 constraint from the same documentation set: among standard units, only &lt;code&gt;Dos&lt;/code&gt;
is overlayable; &lt;code&gt;System&lt;/code&gt;, &lt;code&gt;Overlay&lt;/code&gt;, &lt;code&gt;Crt&lt;/code&gt;, &lt;code&gt;Graph&lt;/code&gt;, &lt;code&gt;Turbo3&lt;/code&gt;, and &lt;code&gt;Graph3&lt;/code&gt;
cannot be overlaid.&lt;/p&gt;
&lt;h2 id=&#34;step-25-when-the-ovr-file-is-actually-created&#34;&gt;Step 2.5: when the &lt;code&gt;.OVR&lt;/code&gt; file is actually created&lt;/h2&gt;
&lt;p&gt;This is the key technical point that is often misunderstood:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;REPORTS.PAS&lt;/code&gt; compiles to &lt;code&gt;REPORTS.TPU&lt;/code&gt; (unit artifact).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAIN.PAS&lt;/code&gt; is compiled and then linked with all used units.&lt;/li&gt;
&lt;li&gt;During &lt;strong&gt;link&lt;/strong&gt;, overlay-managed code is split out and written to one overlay file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So &lt;code&gt;.OVR&lt;/code&gt; is a &lt;strong&gt;link-time output&lt;/strong&gt;, not a unit-compile output.&lt;/p&gt;
&lt;h3 id=&#34;how-code-is-selected-into-ovr&#34;&gt;How code is selected into &lt;code&gt;.OVR&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Selection is not by &amp;ldquo;file extension magic&amp;rdquo; and not by &lt;code&gt;uses Overlay&lt;/code&gt;. The link
pipeline does this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;mark used code blocks from reachable entry points&lt;/li&gt;
&lt;li&gt;check units marked for overlaying (via overlay unit-name directive/options)&lt;/li&gt;
&lt;li&gt;for callable routines in those units, emit call stubs in EXE and write
overlayed code blocks to &lt;code&gt;.OVR&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;unused routines can be omitted entirely&lt;/li&gt;
&lt;li&gt;selected routines from one or more units can end up in the same &lt;code&gt;.OVR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;unit selection is explicit, routine placement is linker-driven from that set&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;naming-rule&#34;&gt;Naming rule&lt;/h3&gt;
&lt;p&gt;The overlay file is tied to the final executable base name, not to a single unit.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;compile/link target &lt;code&gt;MAIN.EXE&lt;/code&gt; -&amp;gt; overlay file &lt;code&gt;MAIN.OVR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;compile/link target &lt;code&gt;APP.EXE&lt;/code&gt; -&amp;gt; overlay file &lt;code&gt;APP.OVR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;REPORTS.OVR&lt;/code&gt; just because &lt;code&gt;Reports&lt;/code&gt; contains overlayed routines.
One executable can include overlayed code from multiple units, and they are
packed into that executable&amp;rsquo;s single overlay payload.&lt;/p&gt;
&lt;h3 id=&#34;when-ovr-may-not-appear&#34;&gt;When &lt;code&gt;.OVR&lt;/code&gt; may not appear&lt;/h3&gt;
&lt;p&gt;If no code is actually emitted as overlayed in the final link result, no &lt;code&gt;.OVR&lt;/code&gt;
file is produced. In that case, check project options/directives first.&lt;/p&gt;
&lt;h2 id=&#34;step-3-build-and-verify-artifacts&#34;&gt;Step 3: build and verify artifacts&lt;/h2&gt;
&lt;p&gt;Build with your normal tool path (IDE or CLI). After successful build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;verify your output executable exists (for example &lt;code&gt;MAIN.EXE&lt;/code&gt; if compiling &lt;code&gt;MAIN.PAS&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;verify matching overlay file exists with the same base name (for example &lt;code&gt;MAIN.OVR&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;record file sizes and timestamp&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;code&gt;.OVR&lt;/code&gt; is missing, your overlay profile is not active.&lt;/p&gt;
&lt;h2 id=&#34;step-4-runtime-tests&#34;&gt;Step 4: runtime tests&lt;/h2&gt;
&lt;h3 id=&#34;test-a---healthy-run&#34;&gt;Test A - healthy run&lt;/h3&gt;
&lt;p&gt;Expected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;startup prints no overlay error&lt;/li&gt;
&lt;li&gt;first &lt;code&gt;R&lt;/code&gt; call may be slower&lt;/li&gt;
&lt;li&gt;repeated &lt;code&gt;R&lt;/code&gt; calls are often faster (buffer reuse)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;test-b---missing-ovr&#34;&gt;Test B - missing OVR&lt;/h3&gt;
&lt;p&gt;Temporarily rename the generated overlay file (for example &lt;code&gt;MAIN.OVR&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Expected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;startup exits with explicit overlay init error&lt;/li&gt;
&lt;li&gt;no undefined behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If it crashes instead, fix error handling before continuing.&lt;/p&gt;
&lt;h2 id=&#34;step-45-initialization-variants-ovrinit-ovrinitems-ovrsetbuf&#34;&gt;Step 4.5: initialization variants (&lt;code&gt;OvrInit&lt;/code&gt;, &lt;code&gt;OvrInitEMS&lt;/code&gt;, &lt;code&gt;OvrSetBuf&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;Minimal initialization:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;OvrInit(OvrFile);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If initialization fails and you still call an overlaid routine, TP5 behavior is
runtime failure (the reference guide calls out runtime error 208).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;OvrInit&lt;/code&gt; practical lookup behavior (TP5): if &lt;code&gt;OvrFile&lt;/code&gt; has no drive/path, the
manager searches current directory, then EXE directory (DOS 3.x), then &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;OvrInit&lt;/code&gt; result handling (&lt;code&gt;OvrResult&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ovrOk&lt;/code&gt;: initialized&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ovrNotFound&lt;/code&gt;: overlay file not found&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ovrError&lt;/code&gt;: invalid overlay format or program has no overlays&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;EMS-assisted initialization:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;OvrInit(OvrFile);
OvrInitEMS;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;OvrInitEMS&lt;/code&gt; can move overlay backing storage to EMS (when available), but
execution still requires copying overlays into the normal-memory overlay buffer.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;OvrInitEMS&lt;/code&gt; result handling (&lt;code&gt;OvrResult&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ovrOk&lt;/code&gt;: overlays loaded into EMS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ovrIOError&lt;/code&gt;: read error while loading overlay file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ovrNoEMSDriver&lt;/code&gt;: no EMS driver detected&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ovrNoEMSMemory&lt;/code&gt;: insufficient free EMS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On &lt;code&gt;OvrInitEMS&lt;/code&gt; errors, overlay manager still runs from disk-backed loading.&lt;/p&gt;
&lt;p&gt;Buffer sizing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TP5 starts with a minimal overlay buffer (large enough for largest overlay).&lt;/li&gt;
&lt;li&gt;For cross-calling overlay groups, this can cause excessive swapping.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OvrSetBuf&lt;/code&gt; increases buffer by shrinking heap.&lt;/li&gt;
&lt;li&gt;legal range (TP5): &lt;code&gt;BufSize &amp;gt;= initial&lt;/code&gt; and &lt;code&gt;BufSize &amp;lt;= MemAvail + OvrGetBuf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;if you increase buffer, adjust &lt;code&gt;{$M ...}&lt;/code&gt; heap minimum accordingly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Important ordering rule (TP5): call &lt;code&gt;OvrSetBuf&lt;/code&gt; while heap is effectively empty.
If using Graph, call &lt;code&gt;OvrSetBuf&lt;/code&gt; before &lt;code&gt;InitGraph&lt;/code&gt;, because &lt;code&gt;InitGraph&lt;/code&gt; allocates
heap memory and can prevent buffer growth.&lt;/p&gt;
&lt;h2 id=&#34;step-5-tune-overlay-buffer-with-measurement&#34;&gt;Step 5: tune overlay buffer with measurement&lt;/h2&gt;
&lt;p&gt;Run the same interaction script while changing &lt;code&gt;OvrSetBuf&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;small buffer (for example 16K)&lt;/li&gt;
&lt;li&gt;medium buffer (for example 32K)&lt;/li&gt;
&lt;li&gt;larger buffer (for example 60K)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Expected pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;too small: frequent reload stalls&lt;/li&gt;
&lt;li&gt;too large: less stall, but memory pressure elsewhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Choose by measured latency and memory headroom, not by guess.&lt;/p&gt;
&lt;h2 id=&#34;step-6-boundary-correction-when-overlay-thrashes&#34;&gt;Step 6: boundary correction when overlay thrashes&lt;/h2&gt;
&lt;p&gt;If one action triggers repeated slowdowns:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;move shared helpers from overlay unit to resident unit&lt;/li&gt;
&lt;li&gt;keep deep cold logic in overlay unit&lt;/li&gt;
&lt;li&gt;reduce cross-calls between overlay units&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Overlay design is call-graph design.&lt;/p&gt;
&lt;h2 id=&#34;troubleshooting-matrix&#34;&gt;Troubleshooting matrix&lt;/h2&gt;
&lt;h3 id=&#34;symptom-unresolved-symbol-at-link&#34;&gt;Symptom: unresolved symbol at link&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;check unit/object participation in link graph&lt;/li&gt;
&lt;li&gt;check far/near and declaration compatibility&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;symptom-startup-overlay-error&#34;&gt;Symptom: startup overlay error&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;check &lt;code&gt;.OVR&lt;/code&gt; filename/path assumptions&lt;/li&gt;
&lt;li&gt;check deployment directory, not just dev directory&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;symptom-intermittent-slowdown&#34;&gt;Symptom: intermittent slowdown&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;profile call path for overlay churn&lt;/li&gt;
&lt;li&gt;increase buffer or move hot helpers resident&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;what-this-tutorial-teaches-beyond-overlays&#34;&gt;What this tutorial teaches beyond overlays&lt;/h2&gt;
&lt;p&gt;You practice four skills that transfer everywhere:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;define expected behavior before test&lt;/li&gt;
&lt;li&gt;verify artifact set before runtime&lt;/li&gt;
&lt;li&gt;isolate runtime dependencies explicitly&lt;/li&gt;
&lt;li&gt;tune with measured data, not assumptions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Related reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-3-overlays-memory-models-and-link-strategy/&#34;&gt;Turbo Pascal Toolchain, Part 3: Overlays, Memory Models, and Link Strategy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-2-objects-units-and-binary-investigation/&#34;&gt;Turbo Pascal Toolchain, Part 2: Objects, Units, and Binary Investigation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal Toolchain, Part 3: Overlays, Memory Models, and Link Strategy</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-3-overlays-memory-models-and-link-strategy/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Mon, 09 Mar 2026 09:46:27 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-3-overlays-memory-models-and-link-strategy/</guid>
      <description>&lt;p&gt;This article is rewritten to be explicitly source-grounded against the
Turbo Pascal 5.0 Reference Guide (1989), Chapter 13 (&amp;ldquo;Overlays&amp;rdquo;) plus
Appendix B directive entries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Structure map.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version note.&lt;/strong&gt; 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—&lt;code&gt;{$O+}&lt;/code&gt;, 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.&lt;/p&gt;
&lt;h2 id=&#34;why-overlays-existed&#34;&gt;Why overlays existed&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; The overlay manager maintains a buffer in conventional memory.
Overlaid routines live in a separate &lt;code&gt;.OVR&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constraints.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Failure modes.&lt;/strong&gt; Undersized buffer: visible thrashing, multi-hundred-millisecond stalls on each swap. Missing &lt;code&gt;.OVR&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design tradeoffs.&lt;/strong&gt; 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. &lt;strong&gt;Packaging and deployment hazards:&lt;/strong&gt; the &lt;code&gt;.OVR&lt;/code&gt; file must
deploy alongside the &lt;code&gt;.EXE&lt;/code&gt; with a matching base name. ZIP extracts that
place &lt;code&gt;.EXE&lt;/code&gt; in one folder and &lt;code&gt;.OVR&lt;/code&gt; in another, or installers that omit the
&lt;code&gt;.OVR&lt;/code&gt;, produce &lt;code&gt;ovrNotFound&lt;/code&gt; at startup. Document in release notes that
both files must stay together; test packaging on a clean directory.&lt;/p&gt;
&lt;h2 id=&#34;tp5-hard-rules-not-optional-style&#34;&gt;TP5 hard rules (not optional style)&lt;/h2&gt;
&lt;p&gt;For TP5 overlaid programs, these are the baseline rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Overlaid units must be compiled with &lt;code&gt;{$O+}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;At any call to an overlaid routine in another module, all active routines
in the current call chain must use FAR call model.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;{$O unitname}&lt;/code&gt; in the &lt;strong&gt;program&lt;/strong&gt; (after &lt;code&gt;uses&lt;/code&gt;) to select overlaid units.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uses&lt;/code&gt; must list &lt;code&gt;Overlay&lt;/code&gt; before overlaid units.&lt;/li&gt;
&lt;li&gt;Programs with overlaid units must compile to disk (not memory).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TP5 also states that among the listed standard units only &lt;code&gt;Dos&lt;/code&gt; is overlayable;
&lt;code&gt;System&lt;/code&gt;, &lt;code&gt;Overlay&lt;/code&gt;, &lt;code&gt;Crt&lt;/code&gt;, &lt;code&gt;Graph&lt;/code&gt;, &lt;code&gt;Turbo3&lt;/code&gt;, and &lt;code&gt;Graph3&lt;/code&gt; are not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tuning workflow.&lt;/strong&gt; Before enabling overlays, identify cold units (e.g. report
generators, rarely-used wizards) and compile them with &lt;code&gt;{$O+}&lt;/code&gt;. Add &lt;code&gt;{$O unitname}&lt;/code&gt;
one unit at a time and rebuild; verify &lt;code&gt;.OVR&lt;/code&gt; appears and size changes as
expected. &lt;strong&gt;Link-map triage:&lt;/strong&gt; with &lt;code&gt;-Fm&lt;/code&gt; (or equivalent map-file option) the
linker produces a &lt;code&gt;.MAP&lt;/code&gt; file. Overlaid segments appear in a dedicated overlay
region; resident segments stay in the main program listing. If you add
&lt;code&gt;{$O UnitName}&lt;/code&gt; but the map shows that unit&amp;rsquo;s code still in the main program,
the directive did not take effect—often due to placement after &lt;code&gt;uses&lt;/code&gt; or a
compile-to-memory build. If link fails or &lt;code&gt;.OVR&lt;/code&gt; is missing, the overlay
selection is not taking effect—check directive placement and &lt;code&gt;uses&lt;/code&gt; order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Failure when rules are violated.&lt;/strong&gt; Omitting &lt;code&gt;{$O+}&lt;/code&gt; on an overlaid unit:
compiler error. Omitting &lt;code&gt;{$F+}&lt;/code&gt; on a caller in the chain: link may succeed
but runtime can corrupt. Forgetting &lt;code&gt;uses Overlay&lt;/code&gt; before overlaid units: the
Overlay unit&amp;rsquo;s runtime is not linked; overlay manager never initializes.
Compiling to memory: overlay linker path is bypassed; no &lt;code&gt;.OVR&lt;/code&gt; produced.&lt;/p&gt;
&lt;h2 id=&#34;what-o-actually-changes&#34;&gt;What &lt;code&gt;{$O+}&lt;/code&gt; actually changes&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;{$O+}&lt;/code&gt; is not just a marker. TP5 documents concrete codegen precautions:
when calls cross units compiled with &lt;code&gt;{$O+}&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;That detail is the reason &amp;ldquo;works in tiny test, crashes in integrated flow&amp;rdquo;
happens when overlay directives are inconsistent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mechanism.&lt;/strong&gt; Without &lt;code&gt;{$O+}&lt;/code&gt;, a call like &lt;code&gt;DoReport(&#39;Monthly&#39;)&lt;/code&gt; may pass a
pointer to a constant in the code segment. If &lt;code&gt;DoReport&lt;/code&gt; is overlaid and triggers
a swap, the caller&amp;rsquo;s code segment can be evicted; the pointer then points at
overlay buffer contents, not the original string. With &lt;code&gt;{$O+}&lt;/code&gt;, the compiler
emits logic to copy the constant to the stack and pass that address instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constraint.&lt;/strong&gt; &lt;code&gt;{$O unitname}&lt;/code&gt; has no effect inside a unit—it is a program-level
directive. The unit must already be compiled with &lt;code&gt;{$O+}&lt;/code&gt; or the compiler
reports an error. Mixing &lt;code&gt;{$O+}&lt;/code&gt; and &lt;code&gt;{$O-}&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example of the constant-copy hazard.&lt;/strong&gt; In a unit compiled without &lt;code&gt;{$O+}&lt;/code&gt;,
&lt;code&gt;WriteReport(HeaderText)&lt;/code&gt; might pass the address of &lt;code&gt;HeaderText&lt;/code&gt; as stored
in the code segment. If &lt;code&gt;WriteReport&lt;/code&gt; is overlaid and triggers a swap, the
caller&amp;rsquo;s code may be evicted; the callee then reads from wrong memory. With
&lt;code&gt;{$O+}&lt;/code&gt;, the compiler generates a copy to a stack temporary and passes that
address—safe regardless of overlay activity.&lt;/p&gt;
&lt;h2 id=&#34;far-call-requirement-explained-operationally&#34;&gt;FAR-call requirement explained operationally&lt;/h2&gt;
&lt;p&gt;Manual example pattern: &lt;code&gt;MainC -&amp;gt; MainB -&amp;gt; OvrA&lt;/code&gt; where &lt;code&gt;OvrA&lt;/code&gt; is in an overlaid
unit. At call to &lt;code&gt;OvrA&lt;/code&gt;, both &lt;code&gt;MainB&lt;/code&gt; and &lt;code&gt;MainC&lt;/code&gt; are active, so they must use
FAR model too.&lt;/p&gt;
&lt;p&gt;Practical TP5-safe strategy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{$O+,F+}&lt;/code&gt; in overlaid units&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{$F+}&lt;/code&gt; in main program and other units&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TP5 notes the cost is usually limited: one extra stack word per active routine
and one extra byte per call.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FAR vs near implications.&lt;/strong&gt; 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 &lt;code&gt;{$F+}&lt;/code&gt;. For deeply nested call
chains—e.g. main → menu → dialog → validator → report—the stack growth is
&lt;code&gt;2 * depth&lt;/code&gt; bytes. In a 64 KB stack, that is rarely the bottleneck; the overlay
buffer and heap compete more for conventional memory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory budget math.&lt;/strong&gt; A rough breakdown for a typical overlaid TP5 app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DOS + drivers + TSRs: ~100–200 KB (varies)&lt;/li&gt;
&lt;li&gt;Resident code (main, Crt, Graph init, hot units): ~80–150 KB&lt;/li&gt;
&lt;li&gt;Overlay buffer (&lt;code&gt;OvrSetBuf&lt;/code&gt;): 32–64 KB typical, up to &lt;code&gt;MemAvail + OvrGetBuf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Heap (&lt;code&gt;{$M min,max}&lt;/code&gt;): remaining conventional memory&lt;/li&gt;
&lt;li&gt;Stack: usually 16–32 KB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;code&gt;MemAvail&lt;/code&gt; at startup is small, increasing overlay buffer via &lt;code&gt;OvrSetBuf&lt;/code&gt;
reduces heap. Tune with &lt;code&gt;MemAvail&lt;/code&gt; and &lt;code&gt;OvrGetBuf&lt;/code&gt; diagnostics before and
after &lt;code&gt;OvrSetBuf&lt;/code&gt;. &lt;strong&gt;Runtime initialization variants:&lt;/strong&gt; &lt;code&gt;OvrSetBuf&lt;/code&gt; must run
while the heap is empty. Two common orderings: (a) &lt;code&gt;OvrInit&lt;/code&gt; → &lt;code&gt;OvrSetBuf&lt;/code&gt; →
heap consumers (Graph, etc.); or (b) &lt;code&gt;OvrInit&lt;/code&gt; only, accepting the default
buffer. If your program uses Graph, call &lt;code&gt;OvrSetBuf&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; &lt;code&gt;InitGraph&lt;/code&gt;—the
Graph unit allocates large video and font buffers from the heap, which locks
in the overlay buffer size. Late &lt;code&gt;OvrSetBuf&lt;/code&gt; after any heap allocation has
no effect; no runtime error, but the buffer stays at its initial minimum.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Segment implications.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;build-and-selection-flow-tp5&#34;&gt;Build and selection flow (TP5)&lt;/h2&gt;
&lt;p&gt;Minimal structure:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program App;
{$F+}
uses Overlay, Dos, MyColdUnit, MyHotUnit;
{$O MyColdUnit}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Key nuances:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{$O unitname}&lt;/code&gt; has no effect inside a unit.&lt;/li&gt;
&lt;li&gt;It only selects used program units for placement in &lt;code&gt;.OVR&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Unit must already be compiled in &lt;code&gt;{$O+}&lt;/code&gt; state or compiler errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Build/link strategy.&lt;/strong&gt; Overlays are a link-time feature. The pipeline:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compile each unit to &lt;code&gt;.TPU&lt;/code&gt; (with correct &lt;code&gt;{$O+}&lt;/code&gt; for overlaid units).&lt;/li&gt;
&lt;li&gt;Compile the main program; the compiler records overlay directives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Link&lt;/strong&gt; produces &lt;code&gt;.EXE&lt;/code&gt; and &lt;code&gt;.OVR&lt;/code&gt;. The linker segregates code marked for
overlay into the &lt;code&gt;.OVR&lt;/code&gt; file and emits call stubs in the &lt;code&gt;.EXE&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A minimal batch build for an overlaid project:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bat&#34; data-lang=&#34;bat&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;echo&lt;/span&gt; off
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;rem Overlaid build: units first, then main, linker produces EXE+OVR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tpc -B MyColdUnit.pas
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tpc -B MyHotUnit.pas
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tpc -B Main.pas
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;errorlevel&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;goto&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;fail&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;exist&lt;/span&gt; Main.OVR &lt;span class=&#34;k&#34;&gt;echo&lt;/span&gt; WARNING: No .OVR produced - overlay selection may be inactive
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;goto&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;ok&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nl&#34;&gt;fail&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;echo&lt;/span&gt; Build failed
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;exit&lt;/span&gt; /b 1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nl&#34;&gt;ok&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;echo&lt;/span&gt; Main.EXE + Main.OVR ready&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Checklist.&lt;/strong&gt; After a clean build: (1) &lt;code&gt;.EXE&lt;/code&gt; and &lt;code&gt;.OVR&lt;/code&gt; exist; (2) &lt;code&gt;.OVR&lt;/code&gt;
size roughly matches sum of overlaid unit contributions; (3) running without
&lt;code&gt;.OVR&lt;/code&gt; fails explicitly at init, not later with corruption; (4) if using
external &lt;code&gt;.OBJ&lt;/code&gt; modules that participate in overlay call chains, ensure they
use FAR call/return conventions compatible with TP&amp;rsquo;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
&lt;code&gt;.EXE&lt;/code&gt; will ship a broken product.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IDE vs CLI parity.&lt;/strong&gt; Overlay options in the IDE (Compiler → Overlay unit
names, Memory compilation off) must match what a batch build does. If the
IDE build produces &lt;code&gt;.OVR&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;.MAP&lt;/code&gt; for overlay forensics.&lt;/strong&gt; With link map output enabled (e.g.
&lt;code&gt;tpc -Fm&lt;/code&gt; 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 &lt;code&gt;{$O unitname}&lt;/code&gt;—overlaid units should move from main-program
segments into the overlay section. (2) If a unit&amp;rsquo;s code remains in the main
program despite &lt;code&gt;{$O unitname}&lt;/code&gt;, the directive was ignored (check placement,
compile-to-disk, &lt;code&gt;uses&lt;/code&gt; order). (3) Use segment sizes in the map to estimate
&lt;code&gt;.OVR&lt;/code&gt; size and the minimum &lt;code&gt;OvrSetBuf&lt;/code&gt;; the largest overlay block sets the
floor. Comparing map before and after adding &lt;code&gt;{$O unitname}&lt;/code&gt; confirms which
code moved to the overlay file.&lt;/p&gt;
&lt;h2 id=&#34;runtime-initialization-contract&#34;&gt;Runtime initialization contract&lt;/h2&gt;
&lt;p&gt;Overlay manager must be initialized before first overlaid call:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;OvrInit(&amp;#39;APP.OVR&amp;#39;);
if OvrResult &amp;lt;&amp;gt; ovrOk then Halt(1);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If initialization fails and you still call overlaid code, TP5 behavior is
runtime error 208 (&amp;ldquo;Overlay manager not installed&amp;rdquo;).&lt;/p&gt;
&lt;h3 id=&#34;ovrinit-behavior-tp5&#34;&gt;&lt;code&gt;OvrInit&lt;/code&gt; behavior (TP5)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Opens/initializes overlay file.&lt;/li&gt;
&lt;li&gt;If filename has no path, search includes current directory, EXE directory
(DOS 3.x), and &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Typical errors: &lt;code&gt;ovrError&lt;/code&gt;, &lt;code&gt;ovrNotFound&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;ovrinitems-behavior-tp5&#34;&gt;&lt;code&gt;OvrInitEMS&lt;/code&gt; behavior (TP5)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Attempts to load overlay file into EMS.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;On error, manager keeps functioning with disk-backed overlay loading.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;EMS usage pattern.&lt;/strong&gt; Call &lt;code&gt;OvrInit&lt;/code&gt; first, then &lt;code&gt;OvrInitEMS&lt;/code&gt;. If &lt;code&gt;OvrResult&lt;/code&gt;
is &lt;code&gt;ovrOk&lt;/code&gt; after &lt;code&gt;OvrInitEMS&lt;/code&gt;, the manager uses EMS for overlay storage. On
&lt;code&gt;ovrNoEMSDriver&lt;/code&gt; or &lt;code&gt;ovrNoEMSMemory&lt;/code&gt;, the program continues with disk loading;
no need to fail. EMS reduces load latency on machines with expanded memory
but is optional for correctness. &lt;strong&gt;EMS tradeoffs:&lt;/strong&gt; 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
&lt;code&gt;.OVR&lt;/code&gt; 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 &lt;code&gt;OvrResult&lt;/code&gt; after &lt;code&gt;OvrInitEMS&lt;/code&gt;; if it is &lt;code&gt;ovrNoEMSMemory&lt;/code&gt;,
consider reducing overlay count or advising users with low EMS to free
expanded memory. On machines without EMS, &lt;code&gt;OvrInitEMS&lt;/code&gt; returns &lt;code&gt;ovrNoEMSDriver&lt;/code&gt;
and the program silently continues with disk—no special handling required.&lt;/p&gt;
&lt;h3 id=&#34;ovrresult-semantics&#34;&gt;&lt;code&gt;OvrResult&lt;/code&gt; semantics&lt;/h3&gt;
&lt;p&gt;Unlike &lt;code&gt;IOResult&lt;/code&gt;, TP5 documents that &lt;code&gt;OvrResult&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; auto-cleared when
read. You can inspect it directly without first copying.&lt;/p&gt;
&lt;h3 id=&#34;usage-patterns-and-diagnostics&#34;&gt;Usage patterns and diagnostics&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Pattern 1: minimal init with explicit path.&lt;/strong&gt; Avoid search-order surprises by
building the overlay path from the executable location:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure InitOverlays;
var ExeDir, ExeName, ExeExt: PathStr;
begin
  FSplit(ParamStr(0), ExeDir, ExeName, ExeExt);
  OvrInit(ExeDir + ExeName + &amp;#39;.OVR&amp;#39;);
  if OvrResult &amp;lt;&amp;gt; ovrOk then
  begin
    case OvrResult of
      ovrError:   WriteLn(&amp;#39;Overlay format error or program has no overlays&amp;#39;);
      ovrNotFound: WriteLn(&amp;#39;Overlay file not found: &amp;#39;, ExeDir + ExeName + &amp;#39;.OVR&amp;#39;);
      else       WriteLn(&amp;#39;OvrResult=&amp;#39;, OvrResult);
    end;
    Halt(1);
  end;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Pattern 2: EMS-optional with fallback.&lt;/strong&gt; Try EMS first; if it fails, disk
loading still works:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;OvrInit(ExeDir + ExeName + &amp;#39;.OVR&amp;#39;);
if OvrResult &amp;lt;&amp;gt; ovrOk then Halt(1);
OvrInitEMS;  { ignore result: disk loading remains available }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Pattern 3: buffer tuning before heap allocation.&lt;/strong&gt; Call &lt;code&gt;OvrSetBuf&lt;/code&gt; while the
heap is empty. With Graph unit:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;OvrInit(OvrFile);
if OvrResult &amp;lt;&amp;gt; ovrOk then Halt(1);
OvrSetBuf(50000);   { before InitGraph }
InitGraph(...);     { Graph allocates from heap }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;OvrResult&lt;/code&gt; reference (TP5 manual-confirmed):&lt;/strong&gt; &lt;code&gt;ovrOk&lt;/code&gt;, &lt;code&gt;ovrError&lt;/code&gt;,
&lt;code&gt;ovrNotFound&lt;/code&gt;, &lt;code&gt;ovrIOError&lt;/code&gt;, &lt;code&gt;ovrNoEMSDriver&lt;/code&gt;, &lt;code&gt;ovrNoEMSMemory&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;OvrSetBuf&lt;/code&gt; diagnostics.&lt;/strong&gt; The call can fail if the heap is not empty or
&lt;code&gt;BufSize&lt;/code&gt; is out of range. TP5 does not document a dedicated &lt;code&gt;OvrResult&lt;/code&gt;
for &lt;code&gt;OvrSetBuf&lt;/code&gt; failure; practical approach: call &lt;code&gt;OvrSetBuf(DesiredSize)&lt;/code&gt;
early, then check &lt;code&gt;OvrGetBuf&lt;/code&gt; to see if the buffer actually increased. If
&lt;code&gt;OvrGetBuf&lt;/code&gt; stays at the initial size, the request was rejected (heap in
use or size constraint). Add a diagnostic mode that prints &lt;code&gt;MemAvail&lt;/code&gt;,
&lt;code&gt;OvrGetBuf&lt;/code&gt;, and &lt;code&gt;MaxAvail&lt;/code&gt; at startup to support troubleshooting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Initialization ordering variants.&lt;/strong&gt; Three common patterns: (a) &lt;em&gt;Minimal&lt;/em&gt;:
&lt;code&gt;OvrInit(path)&lt;/code&gt; only, accept default buffer—works when overlays are small
and rarely cross-call. (b) &lt;em&gt;Buffer-tuned&lt;/em&gt;: &lt;code&gt;OvrInit&lt;/code&gt; → &lt;code&gt;OvrSetBuf(n)&lt;/code&gt; before
any heap use—required when Graph or other heap consumers follow. (c)
&lt;em&gt;EMS-aware&lt;/em&gt;: &lt;code&gt;OvrInit&lt;/code&gt; → &lt;code&gt;OvrInitEMS&lt;/code&gt; → &lt;code&gt;OvrSetBuf&lt;/code&gt;—EMS can speed loads,
but &lt;code&gt;OvrSetBuf&lt;/code&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;how-the-overlay-unit-lays-out-memory-brief&#34;&gt;How the Overlay unit lays out memory (brief)&lt;/h2&gt;
&lt;p&gt;TP5 splits resident and overlaid code at artifact level:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.EXE&lt;/code&gt;: resident (non-overlaid) program parts&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.OVR&lt;/code&gt;: overlaid units selected by &lt;code&gt;{$O unitname}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At runtime, overlaid code executes from a dedicated overlay buffer in
conventional memory. Manual-confirmed points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;initial buffer size is the smallest workable value: the largest overlay
(including fix-up information)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OvrSetBuf&lt;/code&gt; changes buffer size by taking/releasing heap space&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OvrSetBuf&lt;/code&gt; requires an empty heap to take effect&lt;/li&gt;
&lt;li&gt;manager tries to keep as many overlays resident as possible and discards
inactive overlays first when space is needed&lt;/li&gt;
&lt;li&gt;with EMS (&lt;code&gt;OvrInitEMS&lt;/code&gt;), overlays are still copied into normal memory buffer
before execution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Linker behavior (manual-confirmed).&lt;/strong&gt; The TP5 overlay linker produces one
&lt;code&gt;.OVR&lt;/code&gt; per executable. All units marked with &lt;code&gt;{$O unitname}&lt;/code&gt; 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 &lt;code&gt;.OVR&lt;/code&gt; 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 &lt;code&gt;OvrGetBuf&lt;/code&gt; after init reflects the runtime&amp;rsquo;s
minimum—the size of the largest block the manager must load in one swap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FAR/near and overlay placement.&lt;/strong&gt; 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&amp;rsquo;s segment. Near calls in that chain would leave a truncated
return address and corrupt the stack. The constraint applies to the &lt;em&gt;entire&lt;/em&gt;
active call chain at the moment of the overlaid call: main → menu → dialog →
validator → report. If &lt;code&gt;report&lt;/code&gt; is overlaid, every routine in that path must
use FAR. A single near caller in the chain—e.g. a quick helper compiled with
&lt;code&gt;{$F-}&lt;/code&gt;—can cause intermittent crashes when control returns through that
frame; the stack ends up with a mismatched segment.&lt;/p&gt;
&lt;h2 id=&#34;buffer-economics-ovrgetbuf-and-ovrsetbuf&#34;&gt;Buffer economics: &lt;code&gt;OvrGetBuf&lt;/code&gt; and &lt;code&gt;OvrSetBuf&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;TP5 starts with a minimal buffer sized to the largest overlay (including fix-up
data). For cross-calling overlay clusters, this can thrash badly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;OvrSetBuf&lt;/code&gt; tunes buffer size, with constraints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BufSize&lt;/code&gt; must be &amp;gt;= initial size&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BufSize&lt;/code&gt; must be &amp;lt;= &lt;code&gt;MemAvail + OvrGetBuf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;heap must be empty, otherwise call returns error/has no effect&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Important ordering rule: if Graph is used, call &lt;code&gt;OvrSetBuf&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt;
&lt;code&gt;InitGraph&lt;/code&gt; because Graph allocates heap memory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tuning workflow.&lt;/strong&gt; (1) At startup, log &lt;code&gt;MemAvail&lt;/code&gt; and &lt;code&gt;OvrGetBuf&lt;/code&gt; before any
&lt;code&gt;OvrSetBuf&lt;/code&gt;. (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 &lt;code&gt;MemAvail&lt;/code&gt; drops
unsafely. (5) Adjust &lt;code&gt;{$M min,max}&lt;/code&gt; if the larger buffer causes heap
shortage during normal operation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Practical overlay tuning checklist:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Step&lt;/th&gt;
          &lt;th&gt;Action&lt;/th&gt;
          &lt;th&gt;Success criteria&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;1&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;OvrGetBuf&lt;/code&gt; after init&lt;/td&gt;
          &lt;td&gt;Know baseline buffer size&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2&lt;/td&gt;
          &lt;td&gt;Run cold-path sequence 3×&lt;/td&gt;
          &lt;td&gt;Count noticeable pauses&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;3&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;OvrSetBuf(2 * OvrGetBuf)&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Fewer pauses&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;4&lt;/td&gt;
          &lt;td&gt;Iterate until smooth or &lt;code&gt;MemAvail&lt;/code&gt; &amp;lt; 20K&lt;/td&gt;
          &lt;td&gt;Balanced&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Concrete sizing examples.&lt;/strong&gt; 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. &lt;code&gt;OvrSetBuf(48000)&lt;/code&gt; holds
both; transitions become in-memory. If &lt;code&gt;MemAvail&lt;/code&gt; at startup is 120 KB,
reserving 48 KB for overlays leaves ~72 KB for heap—adequate for many apps.
If &lt;code&gt;MemAvail&lt;/code&gt; is 40 KB, a 48 KB buffer request may fail or leave almost no
heap; tune down or reduce resident code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Buffer and Graph/BGI interaction.&lt;/strong&gt; The Graph unit allocates video buffers,
font caches, and driver data from the heap at &lt;code&gt;InitGraph&lt;/code&gt; time. If you call
&lt;code&gt;OvrSetBuf&lt;/code&gt; after &lt;code&gt;InitGraph&lt;/code&gt;, 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: &lt;code&gt;OvrInit&lt;/code&gt;
→ &lt;code&gt;OvrSetBuf&lt;/code&gt; → &lt;code&gt;InitGraph&lt;/code&gt; (or other heap consumers). See &lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-4-graphics-drivers-bgi-and-rendering-integration/&#34;&gt;Part 4: BGI
integration&lt;/a&gt;
for graphics-specific overlay notes.&lt;/p&gt;
&lt;h2 id=&#34;failure-triage-and-performance-profiling-mindset&#34;&gt;Failure triage and performance profiling mindset&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Symptom → check → fix:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Link error / unresolved overlay symbol:&lt;/strong&gt; Unit not in overlay selection, or
mixed far/near in external &lt;code&gt;.OBJ&lt;/code&gt;. Verify &lt;code&gt;{$O unitname}&lt;/code&gt; and &lt;code&gt;{$F+}&lt;/code&gt; on
all units in the call chain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Error 208 at runtime:&lt;/strong&gt; Overlay manager not installed. Either &lt;code&gt;OvrInit&lt;/code&gt; was
never called, or it failed and execution continued. Add init check before
any overlaid call.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ovrNotFound&lt;/code&gt; at startup:&lt;/strong&gt; Path wrong. Use &lt;code&gt;FSplit(ParamStr(0), ...)&lt;/code&gt; to
build overlay path from EXE location; avoid relying on current directory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;ovrError&lt;/code&gt; at startup:&lt;/strong&gt; &lt;code&gt;.OVR&lt;/code&gt; does not match &lt;code&gt;.EXE&lt;/code&gt; (rebuilt one but not
the other), or program has no overlays. Clean rebuild both, verify &lt;code&gt;.OVR&lt;/code&gt;
exists.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Intermittent slowdown / visible stalls:&lt;/strong&gt; Buffer thrashing. Profile by
repeating the slow action and measuring; increase &lt;code&gt;OvrSetBuf&lt;/code&gt; or 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.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Performance profiling mindset.&lt;/strong&gt; Overlay cost is load time, not execution
time. A loaded overlay runs at full speed. &lt;strong&gt;Latency profiling workflow:&lt;/strong&gt;
(1) isolate the user action that triggers the stall; (2) wrap the suspect
call in &lt;code&gt;GetTime&lt;/code&gt;/&lt;code&gt;GetMsCount&lt;/code&gt; 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 (&lt;code&gt;GetTime&lt;/code&gt; before/after) confirms whether the stall aligns with
overlay load. Minimal diagnostic snippet:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;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(&amp;#39;Elapsed: &amp;#39;, EndTotal - StartTotal, &amp;#39; centiseconds&amp;#39;);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;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 &amp;ldquo;slow menu&amp;rdquo; complaints with overlay activity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LRU behavior in practice.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;h2 id=&#34;migration-from-non-overlay-projects&#34;&gt;Migration from non-overlay projects&lt;/h2&gt;
&lt;p&gt;Converting a working non-overlaid program to use overlays:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Identify cold units.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add &lt;code&gt;{$O+}&lt;/code&gt; and &lt;code&gt;{$F+}&lt;/code&gt;&lt;/strong&gt; to candidate units. Add &lt;code&gt;{$F+}&lt;/code&gt; to main program
and any unit that calls overlaid code (directly or transitively).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add &lt;code&gt;uses Overlay&lt;/code&gt;&lt;/strong&gt; as first unit in the main program. Add &lt;code&gt;{$O UnitName}&lt;/code&gt;&lt;br&gt;
for each cold unit, one at a time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enable compile-to-disk&lt;/strong&gt; if building in IDE (Options → Compiler → Directories
or equivalent).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add init block&lt;/strong&gt; before first overlaid call. Use &lt;code&gt;FSplit&lt;/code&gt; + &lt;code&gt;OvrInit&lt;/code&gt; +
&lt;code&gt;OvrResult&lt;/code&gt; check.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clean rebuild.&lt;/strong&gt; Verify &lt;code&gt;.EXE&lt;/code&gt; and &lt;code&gt;.OVR&lt;/code&gt; both produced. Run missing-OVR
test. Run overlay-thrash test and tune &lt;code&gt;OvrSetBuf&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Regression test&lt;/strong&gt; the full feature set. Overlays change memory layout;
subtle bugs (e.g. uninitialized pointers, stack overflow) can surface.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Rollback:&lt;/strong&gt; Remove &lt;code&gt;uses Overlay&lt;/code&gt;, &lt;code&gt;{$O unitname}&lt;/code&gt;, and &lt;code&gt;{$O+}&lt;/code&gt; from overlaid
units; reduce &lt;code&gt;{$F+}&lt;/code&gt; if no longer needed. Rebuild; &lt;code&gt;.OVR&lt;/code&gt; will not be produced,
all code returns to &lt;code&gt;.EXE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Incremental migration.&lt;/strong&gt; 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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Common migration pitfalls.&lt;/strong&gt; (a) Overlaying a unit that is used by many
others—transitive callers all need &lt;code&gt;{$F+}&lt;/code&gt;. (b) Forgetting &lt;code&gt;{$O+}&lt;/code&gt; on one
unit in a cluster—inconsistent codegen can cause pointer corruption. (c)
Deploying &lt;code&gt;.EXE&lt;/code&gt; without &lt;code&gt;.OVR&lt;/code&gt;—build and packaging scripts must include both.
(d) Calling overlaid code before &lt;code&gt;OvrInit&lt;/code&gt;—e.g. from unit initialization
sections—crashes; init must run in the main program before any overlaid
routine is invoked. (e) &lt;strong&gt;Packaging hazards:&lt;/strong&gt; self-extracting archives that
copy only &lt;code&gt;.EXE&lt;/code&gt; files, installers with file filters that exclude &lt;code&gt;.OVR&lt;/code&gt;, or
ZIP-based distributions where users extract to different folders—all produce
&lt;code&gt;ovrNotFound&lt;/code&gt;. Include both files in every distribution artifact; add a
post-install check that verifies &lt;code&gt;EXE_dir + base_name + &#39;.OVR&#39;&lt;/code&gt; exists, or
document clearly that the program requires both files in the same directory.&lt;/p&gt;
&lt;h2 id=&#34;what-is-manual-confirmed-vs-inferred&#34;&gt;What is manual-confirmed vs inferred&lt;/h2&gt;
&lt;p&gt;Manual-confirmed in TP5:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;directive rules (&lt;code&gt;$O+&lt;/code&gt;, &lt;code&gt;$O unitname&lt;/code&gt;, &lt;code&gt;$F+&lt;/code&gt; guidance)&lt;/li&gt;
&lt;li&gt;compile-to-disk requirement&lt;/li&gt;
&lt;li&gt;runtime API behavior (&lt;code&gt;OvrInit&lt;/code&gt;, &lt;code&gt;OvrInitEMS&lt;/code&gt;, &lt;code&gt;OvrSetBuf&lt;/code&gt;, &lt;code&gt;OvrResult&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;FAR-chain safety requirement and consequences&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Intentionally &lt;strong&gt;not&lt;/strong&gt; claimed here as fixed TP5 public spec:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;detailed byte-level &lt;code&gt;.OVR&lt;/code&gt; file format guarantees&lt;/li&gt;
&lt;li&gt;universal behavior across TP6/TP7/BP7 variants without version checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those may be explored, but should be treated as version-scoped reverse engineering.&lt;/p&gt;
&lt;h2 id=&#34;engineering-checklist&#34;&gt;Engineering checklist&lt;/h2&gt;
&lt;p&gt;Before shipping an overlaid TP5 build:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;verify overlaid units compiled with &lt;code&gt;{$O+}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;verify FAR-call policy (&lt;code&gt;{$F+}&lt;/code&gt; strategy) across active-call paths&lt;/li&gt;
&lt;li&gt;verify &lt;code&gt;{$O unitname}&lt;/code&gt; directives and &lt;code&gt;uses Overlay&lt;/code&gt; ordering&lt;/li&gt;
&lt;li&gt;verify &lt;code&gt;.EXE&lt;/code&gt; and &lt;code&gt;.OVR&lt;/code&gt; artifact pair in package&lt;/li&gt;
&lt;li&gt;run one missing-OVR startup test and confirm controlled failure path&lt;/li&gt;
&lt;li&gt;run one overlay-thrash workload and tune with &lt;code&gt;OvrSetBuf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;log &lt;code&gt;MemAvail&lt;/code&gt; and &lt;code&gt;OvrGetBuf&lt;/code&gt; at startup for support diagnostics&lt;/li&gt;
&lt;li&gt;document &lt;code&gt;OvrSetBuf&lt;/code&gt; value and &lt;code&gt;{$M}&lt;/code&gt; in build notes&lt;/li&gt;
&lt;li&gt;include &lt;code&gt;.OVR&lt;/code&gt; in installer and distribution package; document that
&lt;code&gt;.EXE&lt;/code&gt; and &lt;code&gt;.OVR&lt;/code&gt; must stay together&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Deployment note.&lt;/strong&gt; End users rarely see &lt;code&gt;.OVR&lt;/code&gt; files. Installer scripts
and ZIP distributions must include both &lt;code&gt;.EXE&lt;/code&gt; and &lt;code&gt;.OVR&lt;/code&gt; with matching
base names. A self-extracting archive or installer that only grabs the
&lt;code&gt;.EXE&lt;/code&gt; will produce a program that fails at startup with &lt;code&gt;ovrNotFound&lt;/code&gt;.
&lt;strong&gt;Packaging/deployment hazards:&lt;/strong&gt; (1) Build scripts that copy &lt;code&gt;*.EXE&lt;/code&gt; but not
&lt;code&gt;*.OVR&lt;/code&gt; into a release directory. (2) Version-control or backup systems that
ignore &lt;code&gt;*.OVR&lt;/code&gt; by default. (3) Users running from a network drive where the
&lt;code&gt;.OVR&lt;/code&gt; lives on a different path than the &lt;code&gt;.EXE&lt;/code&gt;. (4) Multi-directory
installs (e.g. EXE in &lt;code&gt;\bin&lt;/code&gt;, OVR in &lt;code&gt;\data&lt;/code&gt;) without updating the overlay
search path—&lt;code&gt;OvrInit&lt;/code&gt; with no path uses current directory and EXE directory;
explicit path construction via &lt;code&gt;ParamStr(0)&lt;/code&gt; avoids ambiguity. Add a
pre-release checklist item: verify both artifacts exist in the shipped package.&lt;/p&gt;
&lt;h2 id=&#34;read-next&#34;&gt;Read next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-overlay-tutorial-build-package-and-debug-an-ovr-application/&#34;&gt;Turbo Pascal Overlay Tutorial: Build, Package, and Debug an OVR Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-2-objects-units-and-binary-investigation/&#34;&gt;Turbo Pascal Toolchain, Part 2: Objects, Units, and Binary Investigation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-4-graphics-drivers-bgi-and-rendering-integration/&#34;&gt;Turbo Pascal Toolchain, Part 4: Graphics Drivers, BGI, and Rendering Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
