<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Dos on TurboVision</title>
    <link>https://turbovision.in6-addr.net/tags/dos/</link>
    <description>Recent content in Dos 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/dos/index.xml" rel="self" type="application/rss&#43;xml" />
    
    
    
    <item>
      <title>Turbo Pascal Toolchain, Part 6: Object Pascal, TPW, and the Windows Transition</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-6-object-pascal-tpwin-and-the-windows-transition/</link>
      <pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Fri, 13 Mar 2026 00:00:00 +0000</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-6-object-pascal-tpwin-and-the-windows-transition/</guid>
      <description>&lt;p&gt;Parts 1–5 mapped the DOS-era toolchain: workflow, artifacts, overlays, BGI, and
the compiler/linker boundary from TP6 to TP7. This part crosses the platform
divide. Object Pascal extensions, Turbo Pascal for Windows (TPW), and the move
to message-driven GUIs forced a different kind of toolchain thinking. Same
language family, new mental model.&lt;/p&gt;
&lt;p&gt;This article traces that transition from a practitioner&amp;rsquo;s perspective: what
stayed familiar, what broke, and what had to be relearned. We cover the
historical milestones (TP 5.5 OOP, TPW 1.0, TPW 1.5, BP7), the technical
culprits that bit migrating teams, debugging and build/deploy workflow
differences, and the mental shift from sequential to event-driven execution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version timeline (conservative):&lt;/strong&gt; TP 5.5 (1989) introduced Object Pascal.
TPW 1.0 appeared in the Windows 3.0 era (c. 1991). Borland Pascal 7 (1992)
offered unified DOS and Windows tooling including DLL support. TPW 1.5 followed
TP7 (c. 1993). OWL matured alongside these releases. Exact dates for some
variants vary by region and packaging; the sequence is well established. The
transition spanned roughly four years; many teams maintained both DOS and
Windows targets during that period.&lt;/p&gt;
&lt;h2 id=&#34;structure-map-balanced-chapter-plan&#34;&gt;Structure map (balanced chapter plan)&lt;/h2&gt;
&lt;p&gt;Before drilling into details, this article follows a fixed ten-chapter plan so
the narrative stays balanced rather than front-loaded:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Object Pascal in TP 5.5&lt;/li&gt;
&lt;li&gt;TPW 1.0 and first Windows workflow shock&lt;/li&gt;
&lt;li&gt;TPW 1.5 in the post-TP7 landscape&lt;/li&gt;
&lt;li&gt;BP7 as dual-target toolchain&lt;/li&gt;
&lt;li&gt;OWL and message-driven architecture&lt;/li&gt;
&lt;li&gt;migration culprits and pitfalls&lt;/li&gt;
&lt;li&gt;debugging model changes (DOS vs Windows)&lt;/li&gt;
&lt;li&gt;build/deploy pipeline changes&lt;/li&gt;
&lt;li&gt;team workflow and review-model changes&lt;/li&gt;
&lt;li&gt;synthesis and transfer lessons&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each chapter carries similar depth: technical mechanism, failure mode, and
practical operator/developer workflow.&lt;/p&gt;
&lt;h2 id=&#34;object-pascal-arrives-tp-55-and-the-oop-extensions&#34;&gt;Object Pascal arrives: TP 5.5 and the OOP extensions&lt;/h2&gt;
&lt;p&gt;Turbo Pascal 5.5, released in 1989, introduced Object Pascal: the &lt;code&gt;object&lt;/code&gt; type
with inheritance, virtual methods, and constructors/destructors. The additions
were substantial for the language, but the toolchain remained essentially the
same. Compile, link, run. &lt;code&gt;.TPU&lt;/code&gt; units still carried compiled code; the linker
still produced &lt;code&gt;.EXE&lt;/code&gt;. What changed was what you expressed in those units and
how you structured larger programs.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;object&lt;/code&gt; keyword (distinct from the later &lt;code&gt;class&lt;/code&gt; keyword in Delphi) defined
a type with a hidden pointer to its virtual method table (VMT). Inheritance was
single; you could not inherit from multiple base objects. Virtual methods
required the &lt;code&gt;virtual&lt;/code&gt; directive and had to be overridden with the same
signature. The compiler emitted the VMT layout; if you got the inheritance
hierarchy wrong, the wrong method could be invoked at runtime—a form of bug
that procedural Pascal had never had.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;unit Shapes;

interface

type
  TShape = object
    X, Y: Integer;
    procedure Move(Dx, Dy: Integer);
    procedure Draw; virtual;
    constructor Init(AX, AY: Integer);
    destructor Done; virtual;
  end;

  TCircle = object(TShape)
    Radius: Integer;
    procedure Draw; virtual;
    constructor Init(AX, AY, ARadius: Integer);
  end;

implementation

constructor TShape.Init(AX, AY: Integer);
begin
  X := AX;
  Y := AY;
end;

destructor TShape.Done;
begin
  { cleanup }
end;

procedure TShape.Move(Dx, Dy: Integer);
begin
  Inc(X, Dx);
  Inc(Y, Dy);
end;

procedure TShape.Draw;
begin
  { base: no-op or default behavior }
end;

constructor TCircle.Init(AX, AY, ARadius: Integer);
begin
  TShape.Init(AX, AY);
  Radius := ARadius;
end;

procedure TCircle.Draw;
begin
  { draw circle at X,Y with Radius }
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For DOS projects, this was still a single-threaded, linear-control-flow world.
The object model improved structure and reuse; it did not yet change the
execution paradigm. Overlays, BGI, and conventional memory limits applied
unchanged. Teams adopting Object Pascal in the late 1980s learned inheritance
and polymorphism while keeping familiar toolchain habits.&lt;/p&gt;
&lt;p&gt;Constructor and destructor discipline mattered. In the early &lt;code&gt;object&lt;/code&gt; model
(pre-class syntax), you called &lt;code&gt;Init&lt;/code&gt; explicitly and &lt;code&gt;Done&lt;/code&gt; before disposal.
Forgetting &lt;code&gt;Done&lt;/code&gt; on objects that held resources (handles, memory) leaked. The
toolchain did not enforce this; it was a coding discipline. Virtual method
tables added a small runtime cost and one more thing to get wrong when mixing
object types—passing a &lt;code&gt;TShape&lt;/code&gt; where a &lt;code&gt;TCircle&lt;/code&gt; was expected could produce
subtle bugs if the receiver assumed the concrete type.&lt;/p&gt;
&lt;p&gt;The important point for the Windows transition: Object Pascal gave developers
the vocabulary (inheritance, virtual dispatch, encapsulation) that OWL and later
frameworks would use. Learning OOP in DOS was preparation for OWL&amp;rsquo;s
message-handler hierarchy.&lt;/p&gt;
&lt;p&gt;Toolchain impact was minimal. TP 5.5 still produced &lt;code&gt;.TPU&lt;/code&gt; units; the compiler
emitted VMT layout for object types; the linker resolved virtual calls at
link time. Debugging object hierarchies required understanding the VMT
structure, but Turbo Debugger could display object instances and their
fields. Migration from procedural to object-based code was incremental: one
unit at a time, starting with leaf modules that had no dependencies. A common
path: introduce a single object type to encapsulate a record and its
operations, compile and test, then add inheritance where it simplified
structure. Big-bang rewrites to &amp;ldquo;full OOP&amp;rdquo; were rare and risky; most teams
evolved their codebases gradually.&lt;/p&gt;
&lt;h2 id=&#34;turbo-pascal-for-windows-10-the-first-wave&#34;&gt;Turbo Pascal for Windows 1.0: the first wave&lt;/h2&gt;
&lt;p&gt;Turbo Pascal for Windows 1.0 arrived in the Windows 3.0 era, commonly cited as
around 1991. The toolchain surface looked familiar: blue IDE, integrated
compiler, linker. Underneath, the target was completely different. Instead of
DOS &lt;code&gt;.EXE&lt;/code&gt; and real-mode segments, you produced Windows &lt;code&gt;.EXE&lt;/code&gt; binaries that
linked against the Windows API, expected a GUI entry point (&lt;code&gt;WinMain&lt;/code&gt;), and
ran inside a message loop.&lt;/p&gt;
&lt;p&gt;First-time TPW users discovered that a &amp;ldquo;Pascal program&amp;rdquo; was no longer a
straight-line script. The main block ran once to register the window class,
create the main window, and enter &lt;code&gt;GetMessage&lt;/code&gt;/&lt;code&gt;DispatchMessage&lt;/code&gt;. After that,
everything happened inside the window procedure (&lt;code&gt;WndProc&lt;/code&gt;) in response to
messages. A typical beginner error: putting &amp;ldquo;real&amp;rdquo; logic in the main block,
wondering why it never ran, and only later realizing the block had already
exited into the message loop. Another: assuming that &lt;code&gt;WndProc&lt;/code&gt; would be
called once per &amp;ldquo;event.&amp;rdquo; In fact, Windows sends many messages—&lt;code&gt;WM_CREATE&lt;/code&gt;,
&lt;code&gt;WM_SIZE&lt;/code&gt;, &lt;code&gt;WM_PAINT&lt;/code&gt;, &lt;code&gt;WM_COMMAND&lt;/code&gt;, and dozens more—and the order and
timing depend on user actions and system behaviour. Learning which messages
mattered for a given task was part of the ramp-up.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program HelloWin;

uses
  WinTypes, WinProcs;

const
  IDC_BUTTON = 100;

function WndProc(Window: HWnd; Message, WParam: Word; LParam: LongInt): LongInt;
  far;
begin
  case Message of
    wm_Command:
      if WParam = IDC_BUTTON then
        MessageBox(Window, &amp;#39;Hello from TPW&amp;#39;, &amp;#39;TPW&amp;#39;, mb_Ok);
    wm_Destroy:
      PostQuitMessage(0);
    else
      WndProc := DefWindowProc(Window, Message, WParam, LParam);
      Exit;
  end;
  WndProc := 0;
end;

var
  Msg: TMsg;
  WndClass: TWndClass;
  hWnd: HWnd;

begin
  WndClass.style := 0;
  WndClass.lpfnWndProc := @WndProc;
  WndClass.cbClsExtra := 0;
  WndClass.cbWndExtra := 0;
  WndClass.hInstance := HInstance;
  WndClass.hIcon := LoadIcon(0, idi_Application);
  WndClass.hCursor := LoadCursor(0, idc_Arrow);
  WndClass.hbrBackground := GetStockObject(white_Brush);
  WndClass.lpszMenuName := nil;
  WndClass.lpszClassName := &amp;#39;HelloWin&amp;#39;;

  RegisterClass(WndClass);
  hWnd := CreateWindow(&amp;#39;HelloWin&amp;#39;, &amp;#39;TPW Hello&amp;#39;, ws_OverlappedWindow,
    cw_UseDefault, 0, cw_UseDefault, 0, 0, 0, HInstance, nil);
  ShowWindow(hWnd, sw_ShowNormal);
  UpdateWindow(hWnd);

  while GetMessage(Msg, 0, 0, 0) do
  begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The shift was conceptual: instead of &amp;ldquo;run from top to bottom,&amp;rdquo; you &amp;ldquo;register a
window class, create a window, then sit in a message loop.&amp;rdquo; Event handling was
reactive. The toolchain still produced &lt;code&gt;.EXE&lt;/code&gt;, but the runtime contract was
Windows API calls, far procs, and &lt;code&gt;GetMessage&lt;/code&gt;/&lt;code&gt;DispatchMessage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;TPW 1.0 shipped with &lt;code&gt;WinTypes&lt;/code&gt; and &lt;code&gt;WinProcs&lt;/code&gt; units (API bindings) and
optionally &lt;code&gt;WinCrt&lt;/code&gt; for console-style apps. The IDE looked like the DOS Turbo
Pascal IDE but targeted a different runtime. Keyboard shortcuts and menu
structure were familiar, which eased the transition. The debugger, however,
had to handle a different execution model: breakpoints in message handlers
fired when messages arrived, not when you single-stepped through a linear
flow. Setting a breakpoint in &lt;code&gt;WndProc&lt;/code&gt; and running would eventually stop
there—but only when a message was dispatched to that window. First-time TPW users often hit:
wrong library linking (mixing DOS and Windows units), missing &lt;code&gt;far&lt;/code&gt; on
&lt;code&gt;WndProc&lt;/code&gt;, and confusion about when their code actually ran—the main block
sets up and enters the loop; the rest happens inside &lt;code&gt;WndProc&lt;/code&gt; when messages
arrive. That inversion was the core mental break.&lt;/p&gt;
&lt;p&gt;Linker differences mattered. TPW produced Windows executables with a different
header format, different segment layout, and different startup code. You could
not link a DOS object file into a Windows executable or vice versa. Mixed
projects—e.g. a shared algorithm library—had to compile the same source
twice, once for each target, with target-specific &lt;code&gt;uses&lt;/code&gt; and possibly
&lt;code&gt;{$IFDEF}&lt;/code&gt; guards. The idea of &amp;ldquo;one binary runs everywhere&amp;rdquo; did not exist;
you had DOS binaries and Windows binaries.&lt;/p&gt;
&lt;p&gt;Understanding the message loop was essential. &lt;code&gt;GetMessage&lt;/code&gt; blocks until a
message is available; &lt;code&gt;TranslateMessage&lt;/code&gt; converts keystrokes to &lt;code&gt;WM_CHAR&lt;/code&gt; when
needed; &lt;code&gt;DispatchMessage&lt;/code&gt; invokes the window procedure for the target window.
Every GUI action in a Windows app flows through this pipeline. A handler that
did too much work (e.g. a long computation) would block the loop and freeze
the UI. DOS programs could &lt;code&gt;ReadKey&lt;/code&gt; and wait indefinitely; Windows programs
had to return from handlers quickly and defer heavy work (e.g. via timers or
background processing) to avoid stalling the whole application. Developers
coming from DOS often wrote handlers that performed synchronous file I/O or
lengthy calculations, then wondered why the window would not repaint or
respond to input until the operation finished. The fix was to break work
into smaller chunks or use &lt;code&gt;PeekMessage&lt;/code&gt;-based cooperative multitasking—a
technique that required unlearning the &amp;ldquo;run until done&amp;rdquo; habit.&lt;/p&gt;
&lt;h2 id=&#34;tpw-15-and-the-post-tp7-landscape&#34;&gt;TPW 1.5 and the post-TP7 landscape&lt;/h2&gt;
&lt;p&gt;TPW 1.5 followed TP7 and appeared in the early 1990s (often cited around 1993).
It brought the TP7-era language and tooling to the Windows target. Better
integration with Windows APIs, improved resource tooling, and alignment with the
Borland Pascal 7 family. By this point, DOS and Windows were parallel targets
within the same product family, not separate products with different pedigrees.&lt;/p&gt;
&lt;p&gt;Build workflows diversified. A team might maintain both a DOS and a Windows
configuration: different compiler switches, different libraries, different
entry points. Shared units had to stay abstract enough to compile for both.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Conditional compilation for dual-target units }
unit SharedCore;

interface

procedure DoWork(Data: Pointer);

implementation

{$IFDEF MSWINDOWS}
uses WinTypes, WinProcs;
{$ENDIF}
{$IFDEF MSDOS}
uses Dos;
{$ENDIF}

procedure DoWork(Data: Pointer);
begin
  {$IFDEF MSWINDOWS}
  { Windows-specific implementation }
  {$ENDIF}
  {$IFDEF MSDOS}
  { DOS-specific implementation }
  {$ENDIF}
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;{$IFDEF}&lt;/code&gt; pattern became standard for code shared across targets. Not all
logic could be shared; APIs differed. But data structures, algorithms, and
business rules could live in common units with thin platform-specific wrappers.
Teams learned to minimize &lt;code&gt;{$IFDEF}&lt;/code&gt; surface and push platform branches to
dedicated units.&lt;/p&gt;
&lt;p&gt;A common layout: a &lt;code&gt;Core&lt;/code&gt; unit with pure logic (no &lt;code&gt;uses&lt;/code&gt; of platform units),
a &lt;code&gt;CoreDOS&lt;/code&gt; unit that implemented &lt;code&gt;Core&lt;/code&gt; for DOS (overlays, BGI, &lt;code&gt;Dos&lt;/code&gt; unit),
and a &lt;code&gt;CoreWin&lt;/code&gt; unit that implemented &lt;code&gt;Core&lt;/code&gt; for Windows (handles, &lt;code&gt;WinProcs&lt;/code&gt;).
The program or a top-level unit chose which implementation to use. This kept
the conditional compilation at a few strategic points rather than scattered
throughout.&lt;/p&gt;
&lt;p&gt;TPW 1.5 also improved the resource workflow. Earlier TPW had resource support,
but the integration was rougher. By 1.5, the path from dialog design to linked
&lt;code&gt;.EXE&lt;/code&gt; was more streamlined, and teams doing serious Windows development could
rely on it.&lt;/p&gt;
&lt;p&gt;A practical consideration: machine requirements. DOS Turbo Pascal ran on an
8088 with 256 KB of RAM. TPW and Windows 3.x demanded more—typically a 286 or
386, 1 MB or more of RAM, and a graphics display. Teams developing on
higher-end machines had to remember that target users might have minimal
configurations. Testing on a &amp;ldquo;cramped&amp;rdquo; setup (e.g. 1 MB RAM, 640×480) caught
memory pressure and layout bugs that did not appear on development hardware.&lt;/p&gt;
&lt;h2 id=&#34;bp7-unified-dos-and-windows-toolchain&#34;&gt;BP7: unified DOS and Windows toolchain&lt;/h2&gt;
&lt;p&gt;Borland Pascal 7, released in 1992, provided a single box with DOS and Windows
support. You could build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DOS executables (with overlays, EMS, real-mode semantics)&lt;/li&gt;
&lt;li&gt;Windows executables&lt;/li&gt;
&lt;li&gt;Windows DLLs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DLL building introduced a new artifact type and a new linkage model.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;library MyLib;

uses
  WinTypes, WinProcs;

exports
  MyExportProc index 1,
  MyExportFunc index 2;

procedure MyExportProc(P: PChar); far;
begin
  { DLL-exported procedure }
end;

function MyExportFunc(I: Integer): Integer; far;
begin
  MyExportFunc := I * 2;
end;

begin
  { DLL entry/exit handling if needed }
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The toolchain produced &lt;code&gt;.DLL&lt;/code&gt; instead of (or in addition to) &lt;code&gt;.EXE&lt;/code&gt;. Callers
used &lt;code&gt;LoadLibrary&lt;/code&gt; and &lt;code&gt;GetProcAddress&lt;/code&gt;. Version coupling and calling
conventions mattered more: a Pascal DLL had to match what the caller expected.
Teams learned to isolate DLL interfaces and treat them as stable ABI boundaries.&lt;/p&gt;
&lt;p&gt;DLL entry and exit ran at load/unload. If a DLL&amp;rsquo;s initialization touched
other DLLs or global state, load order could cause subtle failures. Export by
name vs. by ordinal had tradeoffs: ordinals were smaller and faster to resolve
but fragile if the export table changed. Many teams standardized on name-based
exports for maintainability and reserved ordinals for performance-critical
paths. The &lt;code&gt;exports&lt;/code&gt; section in the &lt;code&gt;library&lt;/code&gt; block was the contract; changing
it broke any caller that relied on it. Adding new exports was usually safe;
removing or reordering required coordinated updates to all clients. Teams
that treated the DLL interface as a stable API and versioned it explicitly
(including in documentation) had fewer integration surprises.&lt;/p&gt;
&lt;p&gt;Calling a Pascal DLL from C or another language required matching conventions:
pascal vs. cdecl, near vs. far, and structure layout. Teams building mixed-
language systems documented the ABI explicitly. A small test program that
called each exported function and verified return values caught many
integration bugs before they reached production.&lt;/p&gt;
&lt;p&gt;BP7&amp;rsquo;s value was consolidation: one purchase, one documentation set, one
support channel for both DOS and Windows. Teams could prototype on DOS (faster
iteration, simpler debugging) and port to Windows when the design stabilised,
or maintain both targets from a shared codebase from the start.&lt;/p&gt;
&lt;p&gt;The DLL workflow itself took time to internalise. A &lt;code&gt;library&lt;/code&gt; program had no
main loop; it exported entry points. Callers loaded it, resolved exports, and
called. The DLL&amp;rsquo;s initialization block ran at load; its finalization (if any)
ran at unload. Thread safety was not a primary concern in 16-bit Windows, but
DLL global state was shared across all callers. A bug in one executable&amp;rsquo;s use
of a DLL could corrupt state for another. Documentation and code review had to
cover &amp;ldquo;who loads this DLL, when, and what do they assume about its state?&amp;rdquo;
DLLs also changed the testing matrix: a fix in a shared DLL required
re-testing every application that used it. Versioning the DLL (e.g. embedding
a version resource) and checking it at load time caught many &amp;ldquo;wrong DLL&amp;rdquo;
deployment bugs before they manifested as mysterious crashes.&lt;/p&gt;
&lt;p&gt;Importing a DLL from Pascal required matching the export signature exactly.
A common pattern:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ In unit that uses the DLL }
procedure MyImportProc(P: PChar); far; external &amp;#39;MYLIB&amp;#39; index 1;
function MyImportFunc(I: Integer): Integer; far; external &amp;#39;MYLIB&amp;#39; index 2;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the DLL used &lt;code&gt;pascal&lt;/code&gt; convention (Borland default) and the caller did too,
calls worked. Mixing &lt;code&gt;cdecl&lt;/code&gt; and &lt;code&gt;pascal&lt;/code&gt; caused stack corruption. Teams
building reusable DLLs often documented the calling convention in the header
or in a separate ABI document.&lt;/p&gt;
&lt;h2 id=&#34;owl-and-message-driven-architecture&#34;&gt;OWL and message-driven architecture&lt;/h2&gt;
&lt;p&gt;Object Windows Library (OWL) and similar frameworks wrapped the raw Windows API
in an object-oriented, message-handler style. Instead of a giant &lt;code&gt;case&lt;/code&gt;
statement in a single &lt;code&gt;WndProc&lt;/code&gt;, you subclassed window types and overrode
message handlers.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;unit MyWindow;

interface

uses
  Objects, WinTypes, WinProcs, OWindows;

type
  PMyWindow = ^TMyWindow;
  TMyWindow = object(TWindow)
    procedure WMCommand(var Msg: TMessage); virtual wm_First + wm_Command;
    procedure WMPaint(var Msg: TMessage); virtual wm_First + wm_Paint;
  end;

implementation

procedure TMyWindow.WMCommand(var Msg: TMessage);
begin
  if Msg.WParam = 100 then
    MessageBox(HWindow, &amp;#39;Button clicked&amp;#39;, &amp;#39;OWL&amp;#39;, mb_Ok)
  else
    inherited WMCommand(Msg);
end;

procedure TMyWindow.WMPaint(var Msg: TMessage);
var
  PS: TPaintStruct;
  DC: HDC;
begin
  DC := BeginPaint(HWindow, PS);
  { draw using DC }
  EndPaint(HWindow, PS);
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The pattern: each message maps to a virtual method; &lt;code&gt;inherited&lt;/code&gt; propagates to
the default handler. Toolchain-wise, you still compiled units and linked, but
the design idiom was &amp;ldquo;object per window, method per message.&amp;rdquo; This influenced
how teams structured code and how they debugged: failures showed up as wrong
message routing or missing overrides.&lt;/p&gt;
&lt;p&gt;OWL abstracted the raw &lt;code&gt;RegisterClass&lt;/code&gt;/&lt;code&gt;CreateWindow&lt;/code&gt;/message-loop boilerplate.
You derived from &lt;code&gt;TApplication&lt;/code&gt; and &lt;code&gt;TWindow&lt;/code&gt;, filled in handlers, and the
framework dealt with registration and dispatch. The tradeoff: learning OWL&amp;rsquo;s
object graph and lifecycle. Windows created by OWL were owned by the framework;
manual &lt;code&gt;CreateWindow&lt;/code&gt; calls mixed with OWL could bypass that ownership and cause
duplicate destruction or leaked handles. Teams that went &amp;ldquo;all OWL&amp;rdquo; had fewer
ownership bugs than those that mixed raw API and OWL freely.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;virtual wm_First + wm_Command&lt;/code&gt; syntax mapped a Windows message ID to a
method. When a message arrived, OWL&amp;rsquo;s dispatch logic looked up the method and
called it. If you did not override a message, the base class handled it (or
passed to &lt;code&gt;DefWindowProc&lt;/code&gt;). This was a clean separation of concerns: each
window class handled only the messages it cared about.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ OWL: creating a custom control by inheritance }
type
  PMyEdit = ^TMyEdit;
  TMyEdit = object(TEdit)
    procedure WMChar(var Msg: TMessage); virtual wm_First + wm_Char;
  end;

procedure TMyEdit.WMChar(var Msg: TMessage);
begin
  { Filter or transform input before default handling }
  inherited WMChar(Msg);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This pattern—override, do something, call inherited—became the standard for
extending OWL controls. The toolchain compiled and linked the same way; the
design vocabulary had expanded.&lt;/p&gt;
&lt;p&gt;Choosing between raw API and OWL was a real decision. Raw API gave full
control and smaller binaries but required more boilerplate and discipline.
OWL added framework overhead but let teams ship Windows apps faster. Many
TPW projects started with raw API for learning, then switched to OWL once
the team understood the message model. Hybrid approaches existed but demanded
careful ownership rules for window handles and resources.&lt;/p&gt;
&lt;p&gt;OWL also provided standard dialogs, common controls wrappers, and application
lifecycle management. Reinventing these with raw API was possible but time-
consuming. Teams that adopted OWL early often had a working prototype in days
instead of weeks. The tradeoff was dependency on Borland&amp;rsquo;s framework and its
design decisions; customising behaviour sometimes required diving into OWL
source or working around framework limitations. For teams building multiple
Windows applications, OWL&amp;rsquo;s consistency across projects was valuable: once
you learned the patterns, new apps came together faster. The investment in
learning the framework paid off over several products.&lt;/p&gt;
&lt;h2 id=&#34;technical-culprits-and-pitfalls&#34;&gt;Technical culprits and pitfalls&lt;/h2&gt;
&lt;p&gt;Several failure modes were common when moving from DOS to Windows. Experienced
DOS developers often hit these first; the habits that worked in real mode
backfired in Windows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Far-call discipline.&lt;/strong&gt; Windows callback procs (&lt;code&gt;WndProc&lt;/code&gt;, dialogs, hooks) must
be far. The Windows kernel and USER module invoke your code through function
pointers; in the segmented 16-bit model, a near call to a callback caused
immediate corruption when the system tried to return. Missing &lt;code&gt;far&lt;/code&gt; or wrong
declaration led to crashes that were hard to reproduce—sometimes only when a
particular code path was taken. The compiler did not always catch it; runtime
did, and not always with a clear message.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resource coupling.&lt;/strong&gt; Windows apps depend on &lt;code&gt;.RC&lt;/code&gt; resources (dialogs,
menus, icons). Wrong paths, missing resources, or mismatched IDs produced
obscure startup failures. The linker or resource compiler had to be in the loop,
and the resulting &lt;code&gt;.RES&lt;/code&gt; had to link into the &lt;code&gt;.EXE&lt;/code&gt;. A dialog defined in &lt;code&gt;.RC&lt;/code&gt;
with control ID 100 had to match the &lt;code&gt;wm_Command&lt;/code&gt; handler that checked for 100.
Typos or reuse of IDs across dialogs caused wrong controls to be identified.
Teams learned to centralize ID constants in a shared include or unit. Some
teams used a naming scheme (e.g. &lt;code&gt;IDC_BUTTON_SAVE&lt;/code&gt;, &lt;code&gt;IDC_EDIT_NAME&lt;/code&gt;) to make
the link between resource and handler obvious during code review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Segment and memory model.&lt;/strong&gt; Windows 3.x used segmented memory. Large
allocations, wrong segment assumptions, or stack overflow in message handlers
could corrupt the heap or cause intermittent faults. DOS habits (assume
sequential execution, small stack) did not translate. In DOS, you often knew
exactly when a procedure returned; in Windows, a message handler could call
&lt;code&gt;SendMessage&lt;/code&gt; and re-enter the same or another handler before returning.
Recursive message handling required care with stack depth and static state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;String interop.&lt;/strong&gt; Pascal &lt;code&gt;String[N]&lt;/code&gt; vs. C null-terminated. Windows API
expects &lt;code&gt;PChar&lt;/code&gt; and length conventions. Conversion bugs caused truncation,
buffer overrun, or wrong display. Teams needed explicit conversion layers and
disciplined use of buffers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DLL load order and initialization.&lt;/strong&gt; DLLs had init/exit sequences. Circular
dependencies or incorrect load order led to startup hangs or access violations.
Build order and &lt;code&gt;uses&lt;/code&gt; discipline mattered.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;String conversion and buffer safety.&lt;/strong&gt; Windows API calls often expect
null-terminated &lt;code&gt;PChar&lt;/code&gt;. Pascal &lt;code&gt;String&lt;/code&gt; is length-prefixed. Passing a raw
&lt;code&gt;String&lt;/code&gt; variable where &lt;code&gt;PChar&lt;/code&gt; was expected could work by accident (many
implementations had a trailing zero) but was undefined. Correct pattern:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Safe Pascal-to-Windows string passing }
procedure ShowText(const S: String);
var
  Buf: array[0..255] of Char;
  I: Integer;
begin
  for I := 0 to Length(S) - 1 do
    Buf[I] := S[I + 1];  { Pascal 1-based indexing }
  Buf[Length(S)] := #0;
  MessageBox(0, Buf, &amp;#39;Title&amp;#39;, mb_Ok);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Teams built small conversion units and used them consistently. Ad-hoc &lt;code&gt;StrPCopy&lt;/code&gt;
calls scattered across codebases were a maintenance hazard. A &lt;code&gt;StrUtils&lt;/code&gt; or
&lt;code&gt;WinStrings&lt;/code&gt; unit with &lt;code&gt;PascalToPChar&lt;/code&gt;, &lt;code&gt;PCharToPascal&lt;/code&gt;, and perhaps
&lt;code&gt;PCharBuf&lt;/code&gt; for temporary buffers reduced copy-paste errors and gave a single
place to fix bugs when a new Windows version changed length semantics.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Common mistake: forgetting far on Windows callbacks }
procedure BadProc(Window: HWnd; Msg: Word; W, L: LongInt);  { WRONG }
procedure GoodProc(Window: HWnd; Msg: Word; W, L: LongInt); far;  { CORRECT }&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;debugging-workflows-dos-vs-windows&#34;&gt;Debugging workflows: DOS vs Windows&lt;/h2&gt;
&lt;p&gt;DOS debugging was relatively direct. Single process, linear execution, predictable
crash locations. Turbo Debugger could single-step, set breakpoints, inspect
memory. Overlay and BGI issues were usually reproducible. If a crash happened
at a fixed address, you set a breakpoint there, ran again, and examined the
call stack. Deterministic replay was the default.&lt;/p&gt;
&lt;p&gt;Windows debugging was harder. Message-driven execution meant control flow jumped
between handlers. A bug might only appear when a specific message arrived in a
specific order. Reproducing required driving the UI in a particular way.
Crashes could occur in system code invoked via callback; the immediate cause
might be bad parameters passed from your handler. Null pointer dereferences,
wrong handle usage, and stack corruption in message handlers produced
intermittent failures that did not correlate with &amp;ldquo;run it again.&amp;rdquo;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Diagnostic: log message flow to understand ordering }
procedure TMyWindow.DefaultHandler(var Msg: TMessage);
begin
  WriteLn(DebugFile, &amp;#39;Msg=&amp;#39;, Msg.Msg, &amp;#39; W=&amp;#39;, Msg.WParam, &amp;#39; L=&amp;#39;, Msg.LParam);
  inherited DefaultHandler(Msg);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Practitioners used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OutputDebugString and a monitor (e.g. Turbo Debugger for Windows or
third-party tools) to capture log output&lt;/li&gt;
&lt;li&gt;Conditional breakpoints in the debugger on message IDs (e.g. break when
&lt;code&gt;Msg.Msg = wm_Paint&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Small harness programs that sent specific messages via &lt;code&gt;SendMessage&lt;/code&gt; to
isolate behavior without manual UI interaction&lt;/li&gt;
&lt;li&gt;Map files to correlate addresses with symbols when analyzing postmortem dumps&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mental shift: from &amp;ldquo;re-run until it crashes&amp;rdquo; to &amp;ldquo;instrument and trace
message flow.&amp;rdquo; Debugging became hypothesis-driven: which message, which
window, which order?&lt;/p&gt;
&lt;p&gt;Another technique: build a minimal reproduction. If the bug appeared when
clicking a specific button after resizing the window, create a tiny app with
only that button and that resize logic. Isolating the failure often revealed
that the cause was not where intuition suggested—e.g. a &lt;code&gt;WM_PAINT&lt;/code&gt; handler
that assumed state set up in &lt;code&gt;WM_SIZE&lt;/code&gt;, but &lt;code&gt;WM_PAINT&lt;/code&gt; could arrive before
&lt;code&gt;WM_SIZE&lt;/code&gt; in certain scenarios. Understanding Windows&amp;rsquo; message ordering and
reentrancy was as important as knowing the API. A handler that called
&lt;code&gt;SendMessage&lt;/code&gt; to a child window could find itself re-entered if the child&amp;rsquo;s
handler did something that triggered another message to the parent. Careful
design avoided such cycles; when they occurred, stack overflow or corrupted
state often resulted.&lt;/p&gt;
&lt;h2 id=&#34;build-and-deploy-dos-vs-windows&#34;&gt;Build and deploy: DOS vs Windows&lt;/h2&gt;
&lt;p&gt;DOS deployment was simple: &lt;code&gt;.EXE&lt;/code&gt;, optionally &lt;code&gt;.OVR&lt;/code&gt;, and &lt;code&gt;.BGI&lt;/code&gt;/&lt;code&gt;.CHR&lt;/code&gt; in a
known directory. Batch files or simple install scripts sufficed. A typical
release package: one folder, a few files, run the EXE. Path assumptions (e.g.
&lt;code&gt;.\BGI&lt;/code&gt; for drivers) had to be correct, but the surface was small. Floppy
distribution was common: a single disk for the program, optionally a second
for BGI drivers or overlay files. Users understood &amp;ldquo;copy to C:\MYAPP and run.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Windows deployment added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple DLLs (Windows system DLLs plus any you shipped)&lt;/li&gt;
&lt;li&gt;Resource files (icons, dialogs) embedded or alongside&lt;/li&gt;
&lt;li&gt;INI files or registry for configuration&lt;/li&gt;
&lt;li&gt;Different machine profiles (video drivers, memory)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The resource pipeline was new. You authored &lt;code&gt;.RC&lt;/code&gt; files, compiled them with
&lt;code&gt;BRC.EXE&lt;/code&gt; (Borland Resource Compiler) to &lt;code&gt;.RES&lt;/code&gt;, and linked the &lt;code&gt;.RES&lt;/code&gt; into
the &lt;code&gt;.EXE&lt;/code&gt;. Forgetting the resource step produced a binary that ran but showed
no icon, wrong menu, or broken dialogs. Dialog editor output and hand-written
&lt;code&gt;.RC&lt;/code&gt; had to stay in sync; ID collisions caused mysterious behavior. A small convention helped: define
all resource IDs in a single &lt;code&gt;$I&lt;/code&gt;-included file or a dedicated unit, and
reference them from both &lt;code&gt;.RC&lt;/code&gt; and Pascal. Changing an ID in one place
without the other was a frequent source of &amp;ldquo;the button does nothing&amp;rdquo; bugs
that took hours to track down.&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;/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;c1&#34;&gt;REM DOS build&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 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;copy&lt;/span&gt; main.exe dist\
&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;copy&lt;/span&gt; *.ovr dist\ &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul
&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;copy&lt;/span&gt; bgi\*.bgi dist\bgi\
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&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;c1&#34;&gt;REM Windows build (conceptual)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tpw main.pas
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;brc main.res main.exe
&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;copy&lt;/span&gt; main.exe dist\
&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;copy&lt;/span&gt; mylib.dll dist\ &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul&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;Build scripts had to branch by target. Release builds often required separate
configurations for DOS and Windows, with different linker options and runtime
selection. Teams documented &amp;ldquo;DOS build checklist&amp;rdquo; vs. &amp;ldquo;Windows build checklist&amp;rdquo;
and treated them as separate pipelines. A dual-target product meant two
release builds, two test passes, and two support matrices (e.g. &amp;ldquo;runs on
DOS 5.0+&amp;rdquo; vs. &amp;ldquo;runs on Windows 3.1+&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;Versioning of deliverables also changed. A DOS product might ship &amp;ldquo;v1.2&amp;rdquo;;
a Windows product might need &amp;ldquo;v1.2 for Windows 3.1&amp;rdquo; vs. &amp;ldquo;v1.2 for Windows
3.11&amp;rdquo; if patch-level differences mattered. Installer design entered the
picture: copying files into the right place, registering extensions, and
creating program group icons. Teams that had never needed an &amp;ldquo;install&amp;rdquo; step
had to learn one. Early Windows installers were often batch files or simple
scripts; later, dedicated installer tools (e.g. Borland&amp;rsquo;s own offerings)
became part of the release workflow. The transition from &amp;ldquo;copy to floppy and
run&amp;rdquo; to &amp;ldquo;run setup and follow the wizard&amp;rdquo; was another incremental change that
accumulated over the early 1990s.&lt;/p&gt;
&lt;h2 id=&#34;team-collaboration-and-mental-model-shift&#34;&gt;Team collaboration and mental model shift&lt;/h2&gt;
&lt;p&gt;DOS-era teams had a shared mental model: one process, one flow, predictable
artifacts. Code reviews focused on logic, overlays, and memory. A developer
could read a program from top to bottom and follow execution. Ownership of
&amp;ldquo;the main loop&amp;rdquo; was clear.&lt;/p&gt;
&lt;p&gt;Windows-era teams dealt with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Split expertise: some people owned dialog layout (&lt;code&gt;.RC&lt;/code&gt; and resource
editor), others message handlers, others DLL interfaces. The &amp;ldquo;GUI person&amp;rdquo;
and the &amp;ldquo;engine person&amp;rdquo; became distinct roles.&lt;/li&gt;
&lt;li&gt;Asynchronous feel: events could arrive in varied order; testing had to cover
combinations. &amp;ldquo;Click A then B&amp;rdquo; vs. &amp;ldquo;Click B then A&amp;rdquo; could expose different
bugs.&lt;/li&gt;
&lt;li&gt;Toolchain fragmentation: resource compiler, different linker flags,
different debugger workflows. Build breaks could occur in the resource step,
which DOS-only developers had never seen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Documentation shifted. Instead of &amp;ldquo;run main, then X, then Y,&amp;rdquo; teams wrote &amp;ldquo;on
WM_COMMAND with ID Z, the flow is&amp;hellip;&amp;rdquo;. Architecture diagrams showed window
hierarchies and message flow, not just procedure call graphs. Onboarding
documents included &amp;ldquo;Windows messaging basics&amp;rdquo; and &amp;ldquo;OWL object lifecycle.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;New joiners needed to internalize the event loop and the idea that &amp;ldquo;your code
runs when Windows says so.&amp;rdquo; That was a larger conceptual jump than learning
Object Pascal syntax. Experienced DOS Pascal developers sometimes struggled
more than newcomers—unlearning &amp;ldquo;I control the flow&amp;rdquo; was harder than never
having assumed it.&lt;/p&gt;
&lt;p&gt;Code review practices adapted. DOS reviews often traced &amp;ldquo;what happens when we
run.&amp;rdquo; Windows reviews asked &amp;ldquo;what happens when the user does X, and in what
order do messages arrive?&amp;rdquo; Test plans shifted from &amp;ldquo;run through the menu&amp;rdquo;
to &amp;ldquo;for each dialog, test each control, test tab order, test keyboard
shortcuts.&amp;rdquo; The surface area of &amp;ldquo;things that can go wrong&amp;rdquo; grew
substantially. Senior developers who had debugged DOS programs for years
sometimes needed mentoring from junior developers who had started with
Windows—not because the seniors were less skilled, but because the younger
developers had never internalised the sequential model and adapted to
event-driven design more quickly.&lt;/p&gt;
&lt;p&gt;A practical collaboration upgrade in that period was formal handoff contracts
between UI and engine work. In DOS-only projects, one developer could often own
everything from input parsing to rendering. In TPW projects, that approach
scaled poorly because message handlers, dialog resources, and shared core logic
changed at different speeds. Teams that stayed healthy wrote explicit contracts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which messages a form handled directly versus delegated&lt;/li&gt;
&lt;li&gt;which unit owned validation rules&lt;/li&gt;
&lt;li&gt;which module owned persistence and file I/O&lt;/li&gt;
&lt;li&gt;which callbacks were synchronous, and which were deferred&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without this, &amp;ldquo;small UI tweaks&amp;rdquo; frequently broke core behavior because a
developer moved logic into a handler that now ran under a different timing
context.&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;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&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;Windows handoff note (example)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;-----------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Form: CustomerEdit
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Owner: UI team
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Incoming messages of interest:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WM_INITDIALOG      -&amp;gt; initializes control state
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WM_COMMAND(IDOK)   -&amp;gt; calls ValidateCustomer, then SaveCustomer
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  WM_CLOSE           -&amp;gt; prompts if dirty flag set
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Engine callbacks:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ValidateCustomer(Data): owned by core unit UCustomerRules
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  SaveCustomer(Data): owned by storage unit UCustomerStore
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Invariants:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - SaveCustomer must never run before ValidateCustomer success
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - Dirty flag set only by control-change events
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - Cancel path must not mutate persisted data&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;This kind of document looked heavy for small teams and saved debugging days.
It made expectations executable in reviews and reduced arguments about &amp;ldquo;who
owns this behavior.&amp;rdquo; It also improved onboarding because a new developer could
read one page and understand the current flow before touching code.&lt;/p&gt;
&lt;p&gt;Another change was review vocabulary. DOS reviews asked, &amp;ldquo;Does this procedure
return the right value?&amp;rdquo; Windows reviews increasingly asked, &amp;ldquo;In what callback
context does this run?&amp;rdquo; and &amp;ldquo;What other message paths can trigger this state
change?&amp;rdquo; That second question caught an entire class of defects: duplicated
state transitions caused by one logic block being reachable through both menu
commands and control notifications.&lt;/p&gt;
&lt;p&gt;Teams that developed this callback-context discipline were already preparing for
Delphi&amp;rsquo;s event model, even before switching products. The names changed (&lt;code&gt;OnClick&lt;/code&gt;
instead of &lt;code&gt;WM_COMMAND&lt;/code&gt; branches), but the design concern stayed the same: keep
state transitions explicit, idempotent where possible, and reviewable under
multiple event paths.&lt;/p&gt;
&lt;h2 id=&#34;synthesis-what-the-toolchain-taught&#34;&gt;Synthesis: what the toolchain taught&lt;/h2&gt;
&lt;p&gt;The transition from DOS Turbo Pascal to Object Pascal and TPW was not a
language change alone. The Pascal syntax, unit system, and compilation model
persisted. What changed was the execution environment, the artifact graph, and
the problem-solving strategies. It was a shift in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control flow:&lt;/strong&gt; from sequential to event-driven. Your code became a set of
handlers invoked by the runtime, not a script you controlled from start to
finish.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artifacts:&lt;/strong&gt; from &lt;code&gt;.EXE&lt;/code&gt;+&lt;code&gt;.OVR&lt;/code&gt; to &lt;code&gt;.EXE&lt;/code&gt;+&lt;code&gt;.DLL&lt;/code&gt;+resources. The artifact
graph grew; build and deploy had more moving parts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; from reproducible traces to message-flow analysis. Crashes
became context-dependent; instrumentation and hypothesis replaced simple
replay.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment:&lt;/strong&gt; from single-directory to multi-component, multi-profile.
&amp;ldquo;Works on my machine&amp;rdquo; expanded to &amp;ldquo;works on which video driver, which
memory configuration, which Windows patch level.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The compiler and linker remained recognizable. The surrounding workflow—
resources, callbacks, DLLs, deployment—became the new complexity. Teams that
succeeded treated the Windows toolchain as a different system with different
rules, not &amp;ldquo;Turbo Pascal with a new UI library.&amp;rdquo; The language carried forward;
the problem-solving model had to adapt. Developers who made that mental shift
were well positioned for Delphi and the 32-bit Windows world that followed.
The lessons—event-driven design, resource pipelines, DLL boundaries—carried
forward. Delphi refined the language and tooling, but the conceptual bridge
from DOS to Windows had already been crossed.&lt;/p&gt;
&lt;h2 id=&#34;practical-migration-dos-to-windows-checklist&#34;&gt;Practical migration: DOS to Windows checklist&lt;/h2&gt;
&lt;p&gt;For teams porting an existing DOS application to Windows, a disciplined
sequence reduced risk:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Isolate platform-dependent code.&lt;/strong&gt; Identify all &lt;code&gt;Dos&lt;/code&gt;, &lt;code&gt;Crt&lt;/code&gt;, &lt;code&gt;Graph&lt;/code&gt;,
and overlay usage. Move them behind abstraction layers or &lt;code&gt;{$IFDEF}&lt;/code&gt;-guarded
units.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify string handling.&lt;/strong&gt; Audit every place that touches filenames,
user input, or API parameters. Introduce conversion routines and use them
consistently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add the resource pipeline.&lt;/strong&gt; Create a minimal &lt;code&gt;.RC&lt;/code&gt;, link it, verify the
app still runs. Add dialogs and menus incrementally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replace the main loop.&lt;/strong&gt; The DOS &amp;ldquo;repeat until done&amp;rdquo; loop becomes
&amp;ldquo;register, create, message loop.&amp;rdquo; Ensure no logic assumed it ran &amp;ldquo;at startup&amp;rdquo;
in a single pass.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test on multiple configurations.&lt;/strong&gt; Different video drivers, different
memory, and different Windows versions surfaced bugs that did not appear in
development.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Not every DOS app was worth porting. Those that were tightly coupled to
hardware (TSRs, direct port I/O, mode-X graphics) required substantial redesign
or remained DOS-only. Business logic and data-heavy applications were better
candidates.&lt;/p&gt;
&lt;p&gt;A phased approach often worked: first a Windows shell that displayed data
(perhaps read from a file format shared with the DOS version), then
incremental feature parity. Trying to port everything at once usually led to
long integration branches and merge pain. Teams that shipped a minimal
Windows version early, then iterated, had better feedback and morale.&lt;/p&gt;
&lt;h2 id=&#34;related-reading&#34;&gt;Related reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Turbo Pascal Toolchain, Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&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/turbo-pascal-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/&#34;&gt;Turbo Pascal BGI Tutorial: Dynamic Drivers, Linked Drivers, and Diagnostic Harnesses&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;full-series-index&#34;&gt;Full series index&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-1-anatomy-and-workflow/&#34;&gt;Part 1: Anatomy and Workflow&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;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-3-overlays-memory-models-and-link-strategy/&#34;&gt;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-4-graphics-drivers-bgi-and-rendering-integration/&#34;&gt;Part 4: Graphics Drivers, BGI, and Rendering Integration&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-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 6: Object Pascal, TPW, and the Windows Transition (this article)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-7-from-tpwin-to-delphi-and-the-rad-mindset/&#34;&gt;Part 7: From TPW to Delphi and the RAD Mindset&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal Toolchain, Part 7: From TPW to Delphi and the RAD Mindset</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-7-from-tpwin-to-delphi-and-the-rad-mindset/</link>
      <pubDate>Fri, 13 Mar 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Fri, 13 Mar 2026 00:00:00 +0000</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-7-from-tpwin-to-delphi-and-the-rad-mindset/</guid>
      <description>&lt;p&gt;The transition from Turbo Pascal for Windows (TPW) and Borland Pascal 7 to Delphi
was not merely a product upgrade. It was a mindset shift: from procedural
resource wrangling and manual message dispatch to a visual, component-based, and
event-driven workflow. Developers who had mastered TPW&amp;rsquo;s message loops and
resource scripts found themselves in a different world—one where the form
designer and object inspector replaced the resource editor, and where component
ownership and event handlers replaced explicit handle management.&lt;/p&gt;
&lt;p&gt;This article traces that transition from the perspective of a practitioner who
lived it. It covers workflow changes, delivery model shifts, debugging
adaptations, and team process evolution. The goal is not nostalgia but practical guidance: what to watch for when
migrating, what patterns hold, and what pitfalls to avoid. The TPW-to-Delphi
path was well-traveled in the mid-to-late 1990s; the lessons learned then
remain applicable to any transition from low-level, imperative UI development
to a higher-level, component-based framework. This article assumes familiarity
with TPW or BP7; readers new to that era may find Part 5 and Part 6 of this
series useful for context.&lt;/p&gt;
&lt;h2 id=&#34;structure-map-balanced-chapter-plan&#34;&gt;Structure map (balanced chapter plan)&lt;/h2&gt;
&lt;p&gt;To keep chapter quality even, the article uses a fixed ten-part structure before
going deep into each topic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;historical grounding and chronology boundaries&lt;/li&gt;
&lt;li&gt;what was at stake in workflow terms&lt;/li&gt;
&lt;li&gt;form/resource workflow changes&lt;/li&gt;
&lt;li&gt;component model and package mechanics&lt;/li&gt;
&lt;li&gt;common migration culprits&lt;/li&gt;
&lt;li&gt;build/release pipeline changes&lt;/li&gt;
&lt;li&gt;testing/debugging mindset shift&lt;/li&gt;
&lt;li&gt;architecture consequences&lt;/li&gt;
&lt;li&gt;team-process and delivery-model changes&lt;/li&gt;
&lt;li&gt;migration pattern playbook&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each chapter is intentionally expanded with similar depth: mechanism, pitfalls,
and practical migration guidance.&lt;/p&gt;
&lt;h2 id=&#34;historical-grounding-19931996&#34;&gt;Historical grounding: 1993–1996&lt;/h2&gt;
&lt;p&gt;Delphi development started internally at Borland around 1993. The first public
release shipped in February 1995. That release introduced the Visual Component
Library (VCL), which became the central framework for visual, event-driven
Windows development in Object Pascal. Delphi 2 arrived in 1996 with a strong
focus on 32-bit Windows, consolidating the shift away from 16-bit TPW.&lt;/p&gt;
&lt;p&gt;These dates matter because they bound the technical assumptions. TPW and BP7
targeted 16-bit Windows. Delphi 1 supported 16-bit; Delphi 2 and later targeted
32-bit. Anyone migrating in that window faced both paradigm and platform shifts.&lt;/p&gt;
&lt;p&gt;The competitive landscape also shaped expectations. Visual Basic had established
a visual-design paradigm; Borland&amp;rsquo;s Object Windows Library (OWL) offered an
object-oriented wrapper over the Windows API but remained close to the message
model. Delphi positioned itself between the two: more structured than VB, more
visual than raw OWL. The VCL was the differentiator—a single framework that
unified visual design, component reuse, and compiled performance. Delphi 1
supported 16-bit Windows; migration from TPW could proceed without an immediate
32-bit requirement. Delphi 2&amp;rsquo;s 32-bit focus, arriving in 1996, aligned with
Windows 95&amp;rsquo;s dominance and made the 16-bit path a legacy concern for most new
development. The choice of Object Pascal rather than C++ for the VCL reflected Borland&amp;rsquo;s
heritage and the language&amp;rsquo;s suitability for rapid development: a simpler
object model, predictable destruction, and strong typing reduced certain
classes of bugs. The trade-off was less low-level control than C++; for most
business applications, that trade-off was acceptable. The result was a tool
that appealed to both former TPW developers and newcomers from VB or other
environments.&lt;/p&gt;
&lt;h2 id=&#34;what-was-at-stake-from-resource-wrangler-to-form-designer&#34;&gt;What was at stake: from resource wrangler to form designer&lt;/h2&gt;
&lt;p&gt;In TPW, you typically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hand-authored or used resource editors to produce &lt;code&gt;.RC&lt;/code&gt; and &lt;code&gt;.RES&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;wrote &lt;code&gt;WndProc&lt;/code&gt; handlers and message-case logic&lt;/li&gt;
&lt;li&gt;managed child window placement and styling via API calls&lt;/li&gt;
&lt;li&gt;linked and loaded resources explicitly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mental model was &lt;em&gt;imperative&lt;/em&gt;: you told Windows what to do, step by step.
Delphi replaced that with a &lt;em&gt;declarative&lt;/em&gt; model: you placed components on forms,
set properties, and responded to events. The form became the primary unit of
design, not the resource file.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// TPW-era: manual dialog creation and message handling
function DlgProc(Dlg: HWND; Msg: Word; WParam: Word; LParam: LongInt): Bool;
begin
  Result := False;
  case Msg of
    WM_INITDIALOG: begin
      SetWindowText(GetDlgItem(Dlg, ID_EDIT), &amp;#39;&amp;#39;);
      Result := True;
    end;
    WM_COMMAND:
      if LoWord(WParam) = IDOK then begin
        GetDlgItemText(Dlg, ID_EDIT, Buffer, SizeOf(Buffer));
        EndDialog(Dlg, IDOK);
        Result := True;
      end;
  end;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In Delphi, the same interaction is expressed as component events:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure TMainForm btnOKClick(Sender: TObject);
begin
  // Edit1.Text is directly available; no GetDlgItemText
  ProcessInput(Edit1.Text);
  ModalResult := mrOK;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The shift is not cosmetic. Ownership, lifecycle, and coupling all change. In
TPW, you were responsible for ensuring that every control you created was
eventually destroyed and that no dangling handles survived. In Delphi, the
component tree and ownership model handle that—provided you used &lt;code&gt;Create&lt;/code&gt; with
the correct owner. The mental load shifted from &amp;ldquo;did I free everything?&amp;rdquo; to
&amp;ldquo;did I wire the right events and set the right properties?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;A TPW developer who had internalized the message loop could predict exactly
when &lt;code&gt;WM_PAINT&lt;/code&gt; would fire and in what order. Delphi&amp;rsquo;s &lt;code&gt;OnPaint&lt;/code&gt; and &lt;code&gt;Invalidate&lt;/code&gt;
abstracted that; the framework decided when to paint. That abstraction was
liberating for routine UI work but could be frustrating when squeezing out
performance or debugging flicker. Knowing when to drop to &lt;code&gt;WndProc&lt;/code&gt; or
&lt;code&gt;CreateParams&lt;/code&gt; for low-level control became a mark of seniority. Double-buffering,
which reduced flicker in TPW by managing &lt;code&gt;WM_ERASEBKGND&lt;/code&gt; and paint regions, had
VCL analogs (&lt;code&gt;DoubleBuffered&lt;/code&gt;, &lt;code&gt;TBitmap&lt;/code&gt; offscreen drawing), but the control
points were different. Migration often required re-learning where the levers were.
Developers who had tuned TPW apps for smooth animation or rapid repaints often
needed to re-profile in Delphi: the VCL&amp;rsquo;s paint sequence and invalidation
semantics were not identical to raw &lt;code&gt;WM_PAINT&lt;/code&gt; handling. In most cases the
default behavior was sufficient; for performance-critical paths, measuring
before optimizing remained the rule.&lt;/p&gt;
&lt;h2 id=&#34;form-and-resource-workflow-changes&#34;&gt;Form and resource workflow changes&lt;/h2&gt;
&lt;p&gt;TPW projects combined Pascal sources with resource scripts. A typical layout:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MAIN.RC&lt;/code&gt; defined menus, dialogs, string tables&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BRCC.EXE&lt;/code&gt; produced &lt;code&gt;MAIN.RES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$R MAIN.RES&lt;/code&gt; pulled resources into the executable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Form layout was encoded in dialog templates. Moving a button meant editing
coordinates in the &lt;code&gt;.RC&lt;/code&gt; file or using a separate resource editor. Visual
feedback was indirect. A typical TPW session might involve: edit &lt;code&gt;.RC&lt;/code&gt;, run
&lt;code&gt;BRCC&lt;/code&gt;, recompile, run, discover the button was two pixels off, repeat. The
compile-run cycle was fast, but the layout iteration was tedious.&lt;/p&gt;
&lt;p&gt;Delphi introduced the &lt;code&gt;.DFM&lt;/code&gt; (Delphi Form) file: a textual or binary
representation of the form&amp;rsquo;s component tree and properties. The form designer
and the form&amp;rsquo;s object inspector became the primary interface for layout and
configuration. The &lt;code&gt;.DFM&lt;/code&gt; is paired with a &lt;code&gt;.PAS&lt;/code&gt; file that defines the
component event handlers.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Delphi unit: MainForm.pas (conceptual)
unit MainForm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TMainForm = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.DFM}

procedure TMainForm.Button1Click(Sender: TObject);
begin
  ShowMessage(&amp;#39;Value: &amp;#39; + Edit1.Text);
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;{$R *.DFM}&lt;/code&gt; directive embeds the form&amp;rsquo;s binary resource. No separate &lt;code&gt;.RC&lt;/code&gt;
file is needed for the form itself. Dialogs, menus, and layout live in the form
file; the Pascal unit owns the behavior.&lt;/p&gt;
&lt;p&gt;Early Delphi used binary &lt;code&gt;.DFM&lt;/code&gt; by default. The format was compact but opaque;
merging conflicts in version control were difficult. Later versions offered
text-based &lt;code&gt;.DFM&lt;/code&gt;, which improved diffability. Teams doing collaborative form
work learned to prefer textual form storage where possible.&lt;/p&gt;
&lt;p&gt;The form designer also changed the workflow for alignment and layout. Delphi
provided alignment tools, snap-to-grid, and the ability to select multiple
controls and align them as a group. This reduced the tedium of pixel-perfect
placement and made iteration faster.&lt;/p&gt;
&lt;h3 id=&#34;the-object-inspector-and-design-time-behavior&#34;&gt;The object inspector and design-time behavior&lt;/h3&gt;
&lt;p&gt;A TPW developer edited resources in one tool and wrote Pascal in another.
Delphi unified these: selecting a control in the form designer populated the
object inspector with that control&amp;rsquo;s properties and events. Changing &lt;code&gt;Caption&lt;/code&gt;
or &lt;code&gt;Enabled&lt;/code&gt; took effect immediately in the designer. Double-clicking an event
slot (e.g. &lt;code&gt;OnClick&lt;/code&gt;) created a stub handler and jumped to the code. This tight
loop—design, set property, wire event, run—defined the RAD experience.&lt;/p&gt;
&lt;p&gt;Design-time behavior rested on the same component instances that would run at
runtime. A form loaded in the designer was a real &lt;code&gt;TForm&lt;/code&gt; descendant with real
children. Code that assumed a full application context (e.g. &lt;code&gt;Application.MainForm&lt;/code&gt;)
could fail in the designer. The &lt;code&gt;csDesigning in ComponentState&lt;/code&gt; check became a
standard guard for code that should run only at runtime. Custom components that
performed I/O, showed dialogs, or accessed the network in their constructor
needed such guards—otherwise the designer would hang or error when the
component was dropped on a form.&lt;/p&gt;
&lt;h2 id=&#34;the-component-model-and-packages&#34;&gt;The component model and packages&lt;/h2&gt;
&lt;p&gt;VCL is built on &lt;code&gt;TComponent&lt;/code&gt;, which extends &lt;code&gt;TPersistent&lt;/code&gt; and introduces
ownership, naming, and streaming. Components can contain other components; they
participate in design-time and runtime property streaming.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Minimal custom component skeleton
unit MyButton;

interface

uses
  Classes, Controls, StdCtrls;

type
  TMyButton = class(TButton)
  private
    FClickCount: Integer;
  protected
    procedure Click; override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property ClickCount: Integer read FClickCount;
  end;

procedure Register;

implementation

constructor TMyButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FClickCount := 0;
end;

procedure TMyButton.Click;
begin
  Inc(FClickCount);
  inherited Click;
end;

procedure Register;
begin
  RegisterComponents(&amp;#39;Samples&amp;#39;, [TMyButton]);
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Packages (&lt;code&gt;.DPK&lt;/code&gt;) emerged as the unit of distribution for components and
optional runtime modules. A package lists units and required packages; it can
be design-time only, runtime only, or both. This allowed teams to ship
component libraries without recompiling the main application. Design-time
packages extended the IDE with new components and editors; runtime packages
shipped as &lt;code&gt;.BPL&lt;/code&gt; files and reduced application size when shared. The split
meant that a bug fix in a shared component could be deployed by updating the
BPL—if versioning was under control.&lt;/p&gt;
&lt;h3 id=&#34;third-party-components-and-the-ecosystem&#34;&gt;Third-party components and the ecosystem&lt;/h3&gt;
&lt;p&gt;Delphi&amp;rsquo;s component model encouraged a market for third-party controls: grids,
charting, reporting, database-aware widgets. TPW had little equivalent; you
built or hand-rolled most UI. Adopting a commercial component library
accelerated development but introduced dependency risk. Components that
assumed specific VCL versions, or used undocumented interfaces, could break on
upgrade. Teams learned to evaluate components for stability and source
availability, not just features. When a critical component was abandoned by
its vendor, having the source often meant the difference between a fix and a
rewrite.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Example package source (.DPK)
package MyComponents;

{$R *.RES}
{$DESCRIPTION &amp;#39;Custom component library&amp;#39;}

requires
  vcl;

contains
  MyButton in &amp;#39;MyButton.pas&amp;#39;;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The component model also introduced the &lt;em&gt;published&lt;/em&gt; keyword: properties
declared published appear in the object inspector and are streamed to the
&lt;code&gt;.DFM&lt;/code&gt;. This is where design-time configuration meets runtime behavior.&lt;/p&gt;
&lt;p&gt;Understanding the VCL hierarchy helped when extending or debugging components.
&lt;code&gt;TObject&lt;/code&gt; roots the tree; &lt;code&gt;TPersistent&lt;/code&gt; adds streaming and ownership hooks;
&lt;code&gt;TComponent&lt;/code&gt; adds the component container model and design-time support;
&lt;code&gt;TControl&lt;/code&gt; adds visual representation and parent-child layout; &lt;code&gt;TWinControl&lt;/code&gt;
adds the Windows handle. When a form failed to paint or a control behaved
oddly, tracing up this chain often revealed where the contract was violated.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// TForm inherits Handle, Parent, BoundsRect, Paint from TWinControl chain
// Override CreateParams, CreateWnd, WndProc for low-level customization
procedure TMyForm.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or WS_CLIPCHILDREN;
end;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;culprits-and-pitfalls-during-migration&#34;&gt;Culprits and pitfalls during migration&lt;/h2&gt;
&lt;p&gt;Migration from TPW to Delphi was rarely a clean mechanical translation. The
syntax was similar; the runtime model was not. Teams moving in that period
encountered several recurring failure modes. Recognizing them early saved
significant debugging time. What worked in TPW could fail subtly in Delphi,
and the failures were often intermittent—dependent on timing, handle state, or
initialization order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resource and handle confusion.&lt;/strong&gt; TPW code often stored &lt;code&gt;HWND&lt;/code&gt; or &lt;code&gt;HMenu&lt;/code&gt;
values and passed them to API calls. Delphi wraps these in component properties.
Accessing the raw handle is still possible (&lt;code&gt;Handle&lt;/code&gt;, &lt;code&gt;Menu.Handle&lt;/code&gt;), but
component lifetime now governs when that handle is valid. Code that cached
handles across form recreate or destroy cycles could break.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Message loop assumptions.&lt;/strong&gt; TPW applications sometimes relied on custom
message loops or &lt;code&gt;PeekMessage&lt;/code&gt;/&lt;code&gt;GetMessage&lt;/code&gt; patterns. The VCL provides its own
application message loop. Bypassing it or mixing models led to inconsistent
behavior and hard-to-reproduce bugs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;String and type mismatches.&lt;/strong&gt; TPW used ShortString by default. Delphi
introduced &lt;code&gt;AnsiString&lt;/code&gt; as the default string type (in 32-bit Delphi), with
automatic memory management. Code that relied on length-byte semantics or
passed strings to legacy APIs without conversion could fail.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Pitfall: assuming ShortString semantics with AnsiString
procedure LegacyInterop;
var
  S: string;  // AnsiString in 32-bit Delphi
  Buf: array[0..255] of Char;
begin
  S := Edit1.Text;
  // Wrong: AnsiString is null-terminated, not length-prefixed
  // Right: StrPLCopy(Buf, S, High(Buf)); then use Buf for API calls
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Unit initialization order.&lt;/strong&gt; Delphi units have &lt;code&gt;initialization&lt;/code&gt; and
&lt;code&gt;finalization&lt;/code&gt; sections. Dependency order affects startup and shutdown.
Circular unit references, or initialization that assumed a specific load order,
could cause subtle crashes. A unit that allocated resources in &lt;code&gt;initialization&lt;/code&gt;
and freed them in &lt;code&gt;finalization&lt;/code&gt; was generally safe—unless another unit&amp;rsquo;s
&lt;code&gt;initialization&lt;/code&gt; ran later and expected those resources to exist. Debugging
startup crashes often meant tracing the unit load order in the project&amp;rsquo;s &lt;code&gt;uses&lt;/code&gt;
clause and the uses clauses of each unit. Circular references between units
caused compile errors; circular logic in initialization (A init calls B, B init
calls A) caused runtime failure. Breaking cycles by extracting shared code
into a third unit, or deferring init to a later phase, was the standard fix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Over-reliance on global state.&lt;/strong&gt; TPW code often used global variables for
form references and shared data. Delphi encourages form instances and
component ownership. Migrating without refactoring globals led to
re-entrancy and lifetime bugs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Modal vs modeless confusion.&lt;/strong&gt; TPW used &lt;code&gt;DialogBox&lt;/code&gt; for modal dialogs and
&lt;code&gt;CreateWindow&lt;/code&gt; for modeless. Delphi&amp;rsquo;s &lt;code&gt;ShowModal&lt;/code&gt; and &lt;code&gt;Show&lt;/code&gt; map to that, but
the timing of &lt;code&gt;OnShow&lt;/code&gt;, &lt;code&gt;OnActivate&lt;/code&gt;, and &lt;code&gt;OnCreate&lt;/code&gt; differs from the raw
API sequence. Code that assumed a specific order (e.g. painting before data
load) could break. Testing both modal and modeless code paths was essential.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integer and pointer size changes.&lt;/strong&gt; In 16-bit TPW, &lt;code&gt;Integer&lt;/code&gt; and &lt;code&gt;Pointer&lt;/code&gt;
were both 2 bytes (or 4 for far pointers). In 32-bit Delphi, &lt;code&gt;Integer&lt;/code&gt; stayed
4 bytes but &lt;code&gt;Pointer&lt;/code&gt; became 4 bytes in a flat address space. Code that
stuffed pointers into &lt;code&gt;Word&lt;/code&gt; or &lt;code&gt;Integer&lt;/code&gt; for storage could truncate or
corrupt. Using &lt;code&gt;LongInt&lt;/code&gt; or &lt;code&gt;Pointer&lt;/code&gt; explicitly for pointer-sized values
avoided surprises.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RecreateWindow and handle invalidation.&lt;/strong&gt; When a form&amp;rsquo;s &lt;code&gt;RecreateWnd&lt;/code&gt; or
similar mechanism ran (e.g. after changing &lt;code&gt;BorderStyle&lt;/code&gt; or &lt;code&gt;BorderIcons&lt;/code&gt;), the
underlying &lt;code&gt;HWND&lt;/code&gt; was destroyed and recreated. Code that cached the handle in
a variable held a stale value. The pattern &lt;code&gt;if HandleAllocated then&lt;/code&gt; before
using &lt;code&gt;Handle&lt;/code&gt; became a habit.&lt;/p&gt;
&lt;h2 id=&#34;build-and-release-workflow&#34;&gt;Build and release workflow&lt;/h2&gt;
&lt;p&gt;TPW builds were typically driven by the IDE or a small batch script that
invoked the compiler and linker. Output was a single &lt;code&gt;.EXE&lt;/code&gt; or &lt;code&gt;.DLL&lt;/code&gt;.
Delphi preserved that simplicity for many projects but added:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;project files (&lt;code&gt;.DPR&lt;/code&gt;) as the entry point&lt;/li&gt;
&lt;li&gt;form units and &lt;code&gt;{$R *.DFM}&lt;/code&gt; as first-class build inputs&lt;/li&gt;
&lt;li&gt;package builds for component libraries&lt;/li&gt;
&lt;li&gt;conditional compilation and build configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project file (&lt;code&gt;.DPR&lt;/code&gt;) replaced the old &amp;ldquo;main program&amp;rdquo; as the coordination
point. It listed form units, marked which forms were auto-created (and thus
loaded at startup), and could embed conditional compilation for different
build targets. Auto-created forms simplified startup but could slow launch
when many forms were created eagerly. Teams learned to create forms on demand
(&lt;code&gt;Form2 := TForm2.Create(Application); Form2.Show;&lt;/code&gt;) when memory or startup
time mattered.&lt;/p&gt;
&lt;p&gt;A minimal Delphi project:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program MyApp;

uses
  Forms,
  MainForm in &amp;#39;MainForm.pas&amp;#39; {Form1};

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Command-line builds became &lt;code&gt;DCC32.EXE&lt;/code&gt; (32-bit) or &lt;code&gt;DCC.EXE&lt;/code&gt; (16-bit in Delphi 1).
The linker (&lt;code&gt;ILINK32&lt;/code&gt; in 32-bit) consumed object files from the compiler; package
references and external object modules were configured in the project or unit
sources. Release builds often disabled debug info (&lt;code&gt;$D-&lt;/code&gt;), local symbol info (&lt;code&gt;$L-&lt;/code&gt;),
overflow checking (&lt;code&gt;$Q-&lt;/code&gt;), range checking (&lt;code&gt;$R-&lt;/code&gt;), and stack checking (&lt;code&gt;$S-&lt;/code&gt;).
Teams learned to freeze these settings per configuration. Enabling checks in
debug builds caught many bugs before they reached production; disabling them
in release improved performance. The discipline was to fix any violation exposed by checks rather than disabling
checks to silence the error. A build that succeeded with &lt;code&gt;$R+&lt;/code&gt; in one
configuration and failed with it in another indicated a latent bug. Treating
such failures as &amp;ldquo;the check is wrong&amp;rdquo; rather than &amp;ldquo;we need to fix the code&amp;rdquo; was
a common but costly mistake. Range and overflow checks were cheap enough in
debug that the performance argument against them rarely held.&lt;/p&gt;
&lt;p&gt;The shift to 32-bit also meant larger executables and different deployment
considerations—no more overlays, but more reliance on DLLs and packages for
modular delivery. A typical build script might invoke &lt;code&gt;DCC32&lt;/code&gt; with &lt;code&gt;-B&lt;/code&gt; (build
all), &lt;code&gt;-$D-&lt;/code&gt; (no debug info), and &lt;code&gt;-$R-&lt;/code&gt; (no range check) for release. Staging
the correct runtime packages (&lt;code&gt;VCL*.BPL&lt;/code&gt;, &lt;code&gt;RTL*.BPL&lt;/code&gt;) alongside the &lt;code&gt;.EXE&lt;/code&gt;
became part of the release checklist. The build pipeline itself was similar in
spirit to TPW: compile units to object files, link to executable. The difference
was scale—more units, form resources, and optional packages. Automated builds
that had been simple batch files grew into scripts with conditional compilation,
path setup, and post-build steps (e.g. version stamping, resource injection).
Teams that delayed automation paid a tax during release cycles when manual steps
were forgotten or executed in the wrong order.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Project options often embedded in .DPR or a separate .CFG
// Conditional defines for build variants
{$IFDEF RELEASE}
{$D-} {$L-} {$Q-} {$R-} {$S-}
{$OPTIMIZATION ON}
{$ELSE}
{$D+} {$L+} {$Q+} {$R+} {$S+}
{$OPTIMIZATION OFF}
{$ENDIF}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;testing-and-debugging-mentality-shift&#34;&gt;Testing and debugging mentality shift&lt;/h2&gt;
&lt;p&gt;TPW debugging was breakpoint-and-inspect. You set breakpoints, stepped through
&lt;code&gt;WndProc&lt;/code&gt; and message handlers, and used the CPU view when things went wrong.
The event model was explicit; you could trace from message to handler.&lt;/p&gt;
&lt;p&gt;Delphi&amp;rsquo;s event-driven model changed the mental model. A button click did not
map to a single linear path. Events could be chained (e.g. &lt;code&gt;OnChange&lt;/code&gt; triggering
further updates), and the call stack often included VCL framework code. Debuggers
gained form-aware inspection: you could inspect the live form, its components,
and their properties at breakpoints.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Event-driven debugging: understand the call chain
procedure TForm1.Button1Click(Sender: TObject);
begin
  // Set breakpoint here; Sender tells you which button fired
  UpdateStatus;  // May trigger other events
end;

procedure TForm1.UpdateStatus;
begin
  // Breakpoint here to see who called UpdateStatus
  Label1.Caption := ComputeStatus;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A recurring debugging scenario was &amp;ldquo;why did my form not update?&amp;rdquo; In TPW, you
traced &lt;code&gt;WM_PAINT&lt;/code&gt; or &lt;code&gt;InvalidateRect&lt;/code&gt;. In Delphi, you checked whether
&lt;code&gt;Invalidate&lt;/code&gt; or &lt;code&gt;Repaint&lt;/code&gt; was called, whether the control was visible, and
whether &lt;code&gt;OnPaint&lt;/code&gt; was overridden correctly. The data window (inspecting
component properties at breakpoints) became as important as the watch window.
Seeing that &lt;code&gt;Label1.Caption&lt;/code&gt; was empty when you expected text, or that
&lt;code&gt;Edit1.Visible&lt;/code&gt; was &lt;code&gt;False&lt;/code&gt;, often explained the bug without stepping through
framework code.&lt;/p&gt;
&lt;p&gt;The shift also encouraged a different testing approach: rather than
exercising raw message paths, tests targeted event handlers and component
state. Unit testing frameworks were rare in the mid-1990s, but the separation
of event handlers from UI layout made it easier to reason about behavior in
isolation.&lt;/p&gt;
&lt;p&gt;When debugging failed, the CPU view remained the fallback. Crashes in VCL
internals or third-party components often required setting a breakpoint on
exceptions, then inspecting the call stack and registers. The &amp;ldquo;Evaluate/Modify&amp;rdquo;
dialog let you execute expressions and change variables at breakpoints—useful
for testing fixes without recompiling. Teams developed a habit of creating
minimal reproduction cases: a blank form with one or two controls that
exhibited the bug, stripped of application-specific logic.&lt;/p&gt;
&lt;h2 id=&#34;architecture-implications&#34;&gt;Architecture implications&lt;/h2&gt;
&lt;p&gt;RAD and the VCL did not mandate architecture, but they pushed architects toward
certain patterns. Teams that resisted sometimes paid a maintenance tax; teams
that embraced them could scale. The framework rewarded specific ways of
organizing code and penalized others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Persistence and streaming.&lt;/strong&gt; The VCL&amp;rsquo;s streaming system allowed forms and
components to be saved and loaded without hand-written serialization. The
&lt;code&gt;TReader&lt;/code&gt;/&lt;code&gt;TWriter&lt;/code&gt; and &lt;code&gt;DefineProperties&lt;/code&gt; mechanism supported custom data in
components. Component authors who needed to store non-published state could
override &lt;code&gt;DefineProperties&lt;/code&gt; to read and write their data. This was powerful
but easy to get wrong—version mismatches between stored and current property
semantics could corrupt form files. Defensive readers that checked version
numbers or used try/except around property reads were common. Custom components
that stored complex data (e.g. tree structures, graphs) had to decide whether
to use &lt;code&gt;DefineProperties&lt;/code&gt; or separate files. Embedded storage simplified
deployment; separate files allowed formats that could be edited independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event-driven design.&lt;/strong&gt; Logic moved from a central message pump into
distributed event handlers. This improved locality (each component owned its
responses) but could scatter business logic across many handlers. Disciplined
teams extracted core logic into service units or classes, keeping handlers thin.
The &lt;code&gt;Sender&lt;/code&gt; parameter in events allowed one handler to serve multiple controls
(e.g. several buttons sharing an &lt;code&gt;OnClick&lt;/code&gt;), but that pattern could obscure
which control actually fired. Using separate handlers or &lt;code&gt;if Sender = Button1&lt;/code&gt;
kept intent clear. The balance between DRY and readability was project-specific.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Threading and the main thread.&lt;/strong&gt; The VCL was not designed for multi-threaded
UI updates. Modifying control properties or calling UI methods from a worker
thread could cause unpredictable crashes. The rule was: all UI updates must
happen on the main thread. &lt;code&gt;Synchronize&lt;/code&gt; and &lt;code&gt;Queue&lt;/code&gt; (in later Delphi
versions) marshaled work from background threads to the main thread. TPW code
that had used worker threads for long operations had to be adapted to this
model; the logic could stay in the thread, but any UI feedback had to go
through &lt;code&gt;Synchronize&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separation of concerns.&lt;/strong&gt; The form file (&lt;code&gt;.DFM&lt;/code&gt;) held layout and property
defaults; the Pascal unit held behavior. That split made it easier to
version-control and merge changes, though &lt;code&gt;.DFM&lt;/code&gt; binary format could be opaque.
Later Delphi versions supported textual &lt;code&gt;.DFM&lt;/code&gt; for clearer diffs. The
separation also meant that a designer could adjust layout without touching
code, and a developer could change behavior without risking layout. In
practice, the split was porous—event handlers often reached into control
properties, and layout could affect behavior (e.g. tab order, focus). But the
ideal was clear: form for structure, unit for logic. Tab order in particular
caused headaches: the designer set it visually, but adding or removing controls
could scramble the intended flow. Using &lt;code&gt;TabOrder&lt;/code&gt; explicitly, or the tab-order
dialog, was part of the polish that separated finished applications from
prototypes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Component reuse and ownership.&lt;/strong&gt; The &lt;code&gt;Owner&lt;/code&gt; parameter in &lt;code&gt;TComponent.Create&lt;/code&gt;
established parent-child relationships. Destroying a form destroyed its
components. This eliminated many manual cleanup bugs but required understanding
ownership when creating components dynamically. Creating a control with &lt;code&gt;nil&lt;/code&gt;
as owner meant you were responsible for freeing it—a common source of leaks
when the pattern was forgotten. The rule &amp;ldquo;always pass an owner when you have
one&amp;rdquo; became second nature.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Ownership: Created edit is owned by Form1, freed when Form1 is freed
procedure TForm1.AddDynamicEdit;
var
  E: TEdit;
begin
  E := TEdit.Create(Self);  // Self = Form1 = owner
  E.Parent := Self;
  E.Top := 10;
  E.Left := 10;
  E.Text := &amp;#39;Dynamic&amp;#39;;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Dependency direction.&lt;/strong&gt; Well-structured Delphi projects kept business logic
in units that did not depend on &lt;code&gt;Forms&lt;/code&gt; or &lt;code&gt;Controls&lt;/code&gt;. UI units depended on
business units, not the reverse. This preserved testability and reuse.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Good: business logic unit has no UI dependency
unit OrderLogic;

interface
function ValidateOrder(const OrderId: string): Boolean;

implementation
// No Forms, Controls, or Graphics
end.

// UI unit depends on OrderLogic
unit OrderForm;

uses
  ..., OrderLogic;

procedure TOrderForm.btnValidateClick(Sender: TObject);
begin
  if ValidateOrder(edtOrderId.Text) then
    ShowMessage(&amp;#39;Valid&amp;#39;);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Form bloat.&lt;/strong&gt; A common anti-pattern was the &amp;ldquo;god form&amp;rdquo;: one form with dozens
of controls and thousands of lines. Splitting into sub-forms, frames (when
available), or tabbed interfaces required discipline. The RAD temptation was to
keep adding controls; the architectural response was to extract coherent
panels into separate units.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data binding and the missing link.&lt;/strong&gt; Early Delphi did not ship a formal
data-binding framework. Developers manually moved data between controls and
business objects in event handlers. The pattern &amp;ldquo;read from controls, validate,
update model, write back to controls&amp;rdquo; was common. This worked but scattered
synchronization logic. Third-party data-aware controls and later framework
additions addressed some of this; disciplined teams often built thin adapter
layers to centralize the binding logic.&lt;/p&gt;
&lt;h2 id=&#34;delivery-model-and-team-process-changes&#34;&gt;Delivery model and team process changes&lt;/h2&gt;
&lt;p&gt;The RAD promise was faster delivery. The reality was more nuanced.&lt;/p&gt;
&lt;p&gt;TPW projects often had a single developer or a small team with clear handoffs:
one person owned resources, another owned logic. Delphi&amp;rsquo;s RAD workflow
encouraged faster iteration. A developer could design a form, wire events, and
see results without leaving the IDE. That accelerated prototyping but also
tempted teams to skip design—&amp;ldquo;we&amp;rsquo;ll fix it later&amp;rdquo; became a common anti-pattern.&lt;/p&gt;
&lt;p&gt;Delivery cycles shortened. Demo builds could be produced in hours. The flip
side was technical debt: forms with hundreds of controls, event handlers
doing too much, and little automated testing. Teams that adopted coding
standards (handler size limits, mandatory extraction of business logic)
fared better.&lt;/p&gt;
&lt;p&gt;When RAD went wrong, the symptoms were familiar: a form that &amp;ldquo;worked&amp;rdquo; until
you changed one thing and then everything broke; event handlers that called
each other in circular ways; business logic embedded in &lt;code&gt;OnClick&lt;/code&gt; that could
not be tested without spinning up the full form. The remedy was the same as in
non-RAD projects—extract, decompose, test—but the temptation to stay in &amp;ldquo;fast
mode&amp;rdquo; was stronger because the IDE made it easy to keep adding. Senior
developers learned to recognize the moment when a form or handler had crossed
the complexity threshold and needed refactoring.&lt;/p&gt;
&lt;p&gt;Distribution also changed. TPW produced a standalone &lt;code&gt;.EXE&lt;/code&gt; plus any DLLs.
Delphi could do the same, but package-based deployments (runtime packages
like &lt;code&gt;VCL50.BPL&lt;/code&gt;) allowed smaller executables and shared framework updates.
The trade-off was versioning: mismatched package versions caused load failures.
&amp;ldquo;DLL hell&amp;rdquo; extended to packages: installing a new application could overwrite
shared BPLs and break existing ones. Many teams chose static linking for
distribution to avoid that risk.&lt;/p&gt;
&lt;p&gt;Team roles shifted. The &amp;ldquo;resource person&amp;rdquo; role diminished; the &amp;ldquo;form designer&amp;rdquo;
and &amp;ldquo;component author&amp;rdquo; roles emerged. Code reviews began to ask &amp;ldquo;is this
handler too large?&amp;rdquo; and &amp;ldquo;should this logic live in a service unit?&amp;rdquo; Pair
programming, where it existed, often involved one person driving the form
designer while the other focused on event logic and backend integration. The
division was natural: layout and property wrangling on one side, data flow and
validation on the other. Teams that formalized this split—e.g. &amp;ldquo;form designer&amp;rdquo;
and &amp;ldquo;form programmer&amp;rdquo; roles—sometimes produced cleaner boundaries than those
where one person did everything. The risk was handoff friction when the
designer&amp;rsquo;s intent was not clear from the form alone.&lt;/p&gt;
&lt;h2 id=&#34;practical-migration-patterns&#34;&gt;Practical migration patterns&lt;/h2&gt;
&lt;p&gt;When porting TPW code to Delphi, these patterns proved reliable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extract message handlers into event-like procedures.&lt;/strong&gt; Wrap the core logic
in a procedure with clear parameters; call it from both the old &lt;code&gt;WndProc&lt;/code&gt; path
and the new event handler during transition.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure DoProcessInput(const AText: string);
begin
  if Trim(AText) = &amp;#39;&amp;#39; then Exit;
  // Core logic here
end;

// TPW: call from WM_COMMAND handler
// Delphi: call from Button1Click with Edit1.Text&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Introduce form classes gradually.&lt;/strong&gt; Start with a blank form, add controls
one at a time, and move logic from global procedures into form methods. This
avoids big-bang rewrites. Resist the urge to convert all dialogs in one pass.
Pick the simplest dialog first, migrate it, validate, then proceed. Each
successful migration builds confidence and surfaces patterns that apply to
the next.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create a compatibility shim for shared code.&lt;/strong&gt; If both TPW and Delphi
executables need to call the same business logic during transition, extract
that logic into a unit with no UI dependencies. Both projects can use it.
Pass data via parameters, not globals. This keeps the migration reversible
and reduces the risk of fork drift. The shim unit should avoid VCL-specific
types where possible; use plain Pascal types (&lt;code&gt;string&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt;, records)
for interfaces that cross the TPW/Delphi boundary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verify string and API compatibility.&lt;/strong&gt; Use &lt;code&gt;StrPLCopy&lt;/code&gt; and &lt;code&gt;StrPCopy&lt;/code&gt; when
passing strings to Windows API. Check &lt;code&gt;PChar&lt;/code&gt; vs &lt;code&gt;PAnsiChar&lt;/code&gt; in 32-bit Delphi.
Test with empty strings and long strings; ShortString and AnsiString differ at
the boundaries.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Safe API string passing
procedure SafeAPICall(const S: string);
var
  Buf: array[0..259] of AnsiChar;
begin
  StrPLCopy(Buf, AnsiString(S), High(Buf));
  SomeAPI(@Buf[0]);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Lock build configuration early.&lt;/strong&gt; Decide debug vs release, range check on/off,
and optimization level. Document and automate. Avoid ad hoc changes during
release crunches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Migration checklist.&lt;/strong&gt; A practical sequence:&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;/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;1. Inventory TPW dialogs and main windows; map each to a target form.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2. Create empty forms, add controls to match layout, wire stub events.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3. Move message-handler logic into event handlers; extract shared logic.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;4. Replace global form references with Application.FindComponent or parameters.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;5. Audit string types at API boundaries; add StrPLCopy/StrPCopy where needed.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;6. Run under range checking and overflow checking; fix violations first.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;7. Test modal/modeless behavior; verify focus and activation order.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8. Freeze build options; document and script the release build.&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;Use Application.OnMessage sparingly.&lt;/strong&gt; The global message hook can help
during migration to intercept specific messages, but it runs for every message
and can obscure the event-driven flow. Prefer component-level overrides or
message handlers (&lt;code&gt;TForm&lt;/code&gt; supports &lt;code&gt;WM_*&lt;/code&gt; procedure declarations) for targeted
handling.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;// Form-level message handler: more targeted than Application.OnMessage
type
  TMainForm = class(TForm)
  private
    procedure WMUserMsg(var Msg: TMessage); message WM_USER;
  end;

procedure TMainForm.WMUserMsg(var Msg: TMessage);
begin
  // Handle custom message; call inherited for default behavior if needed
  ProcessCustomMessage(Msg.WParam, Msg.LParam);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Preserve TPW project artifacts during transition.&lt;/strong&gt; Keep a known-good TPW
build and its sources in version control. If a Delphi regression appears,
you can compare behavior and isolate whether the bug is in migrated logic or
the new framework. When the migration is complete, archive rather than
delete—historical reference has value for onboarding and retrospective analysis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Treat the first migrated dialog as a prototype.&lt;/strong&gt; Use it to establish
conventions: naming (e.g. &lt;code&gt;btnOK&lt;/code&gt; not &lt;code&gt;Button1&lt;/code&gt;), handler structure, where
validation lives. Document those conventions and apply them consistently. The
first migration is always the hardest; later ones benefit from the patterns
you extract. Skipping the documentation step means each developer reinvents
the approach, and inconsistency makes maintenance harder.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Expect a learning curve for the form designer.&lt;/strong&gt; TPW developers who had
never used a visual designer faced new concepts: alignment palettes, tab
order, anchor and alignment properties (in later Delphi versions), the
difference between selecting the form and selecting a control. Spending a few
hours on throwaway forms to learn alignment, anchoring, and the property
inspector paid off before tackling a real migration. Misunderstanding the
designer led to layout bugs that were hard to fix by hand-editing &lt;code&gt;.DFM&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;first-90-day-delphi-adoption-cadence&#34;&gt;First 90-day Delphi adoption cadence&lt;/h3&gt;
&lt;p&gt;Teams that transitioned cleanly usually followed a staged first-quarter plan,
not an all-at-once rewrite:&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;span class=&#34;lnt&#34;&gt;14
&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;Days 1-30:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - pick one medium-complex form
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - define naming/event conventions
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - establish build options and debug baseline
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Days 31-60:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - migrate 3-5 related dialogs/forms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - extract shared non-UI logic into units
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - add regression checklist for core user flows
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Days 61-90:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - package reusable controls/components
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - document standard form lifecycle hooks
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - formalize release checklist and rollback criteria&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;This cadence solved two chronic problems: premature abstraction and duplicated
mistakes. Premature abstraction happened when teams designed a full internal
&amp;ldquo;framework&amp;rdquo; before they had migrated enough screens to understand recurring
patterns. Duplicated mistakes happened when each developer migrated forms in
isolation with personal conventions. A short, staged cadence turned both into
manageable process work.&lt;/p&gt;
&lt;p&gt;A practical metric during this period was &amp;ldquo;time from UI change request to tested
build.&amp;rdquo; If that time dropped while defect rate stayed stable, Delphi adoption
was producing value. If the time dropped but defect rate climbed, the team was
moving too fast without enough shared conventions.&lt;/p&gt;
&lt;h2 id=&#34;summary-and-outlook&#34;&gt;Summary and outlook&lt;/h2&gt;
&lt;p&gt;The TPW-to-Delphi transition was more than a product upgrade; it was a paradigm
shift in how Windows UI was built: from imperative,
resource-centric Windows development to a visual, event-driven, component-based
model. VCL and the form designer changed how developers conceived of UI, and
the RAD mindset changed delivery expectations. Teams that understood both the
gains (faster iteration, clearer ownership, component reuse) and the pitfalls
(handle lifetime, string types, over-coupled forms) navigated the transition
successfully.&lt;/p&gt;
&lt;p&gt;Delphi&amp;rsquo;s influence extended beyond Borland. The component model, property
inspector, and form designer pattern appeared in other tools and languages.
The Object Pascal language evolved but remained recognizable to TPW
practitioners. For those tracing the Turbo Pascal toolchain into the Windows
era, Delphi is the natural continuation—and the RAD mindset it introduced
still shapes how many think about UI development today. The move from
&amp;ldquo;write code that creates UI&amp;rdquo; to &amp;ldquo;design UI and write code that responds&amp;rdquo;
has informed every major GUI framework since.&lt;/p&gt;
&lt;p&gt;The transition also illustrated a recurring tension in tool evolution: each
abstraction layer buys productivity at the cost of opacity. TPW developers
could read the SDK and understand every message; Delphi developers relied on
the VCL to do the right thing. When the abstraction leaked—handle lifetime,
recreate behavior, focus management—the ability to reason about the lower
level became valuable. The best Delphi practitioners kept that mental model intact. They knew when
to use &lt;code&gt;Sender&lt;/code&gt; in an event to identify the originating control, when to
override &lt;code&gt;WndProc&lt;/code&gt; versus using &lt;code&gt;OnMessage&lt;/code&gt;, and how to trace from a visible
bug back through the message or event chain. That knowledge, built during the
TPW-to-Delphi transition, remained valuable for as long as Windows and the VCL
evolved together.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;related-reading&#34;&gt;Related reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-6-object-pascal-tpwin-and-the-windows-transition/&#34;&gt;Turbo Pascal Toolchain, Part 6: Object Pascal, TPW, and the Windows Transition&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-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Turbo Pascal Toolchain, Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-units-as-architecture/&#34;&gt;Turbo Pascal Units as Architecture, Not Just Reuse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Deterministic DIR Output as an Operational Contract</title>
      <link>https://turbovision.in6-addr.net/retro/dos/deterministic-dir-output-as-an-operational-contract/</link>
      <pubDate>Tue, 10 Mar 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Tue, 10 Mar 2026 00:00:00 +0000</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/deterministic-dir-output-as-an-operational-contract/</guid>
      <description>&lt;p&gt;The story starts at 23:14 in a room with two beige towers, one half-dead fluorescent tube, and a whiteboard covered in hand-written file counts. We had one mission: rebuild a damaged release set from mixed backup disks and compare it against a known-good manifest.&lt;/p&gt;
&lt;p&gt;On paper, that sounds easy. In practice, it meant parsing &lt;code&gt;DIR&lt;/code&gt; output across different machines, each configured slightly differently, each with enough personality to make automation fail at the worst moment.&lt;/p&gt;
&lt;p&gt;By 23:42 we had already hit the first trap. One machine produced &lt;code&gt;DIR&lt;/code&gt; output that looked &amp;ldquo;normal&amp;rdquo; to a human and ambiguous to a parser. Another printed dates in a different shape. A third had enough local customization that every assumption broke after line three. We were not failing because DOS was bad. We were failing because we had not written down what &amp;ldquo;correct output&amp;rdquo; meant.&lt;/p&gt;
&lt;p&gt;That night we stopped treating &lt;code&gt;DIR&lt;/code&gt; as a casual command and started treating it as an API contract.&lt;/p&gt;
&lt;p&gt;This article is that deep dive: why a deterministic profile matters, how to structure it, and how to parse it without superstitions.&lt;/p&gt;
&lt;h2 id=&#34;the-turning-point-formatting-is-behavior&#34;&gt;The turning point: formatting is behavior&lt;/h2&gt;
&lt;p&gt;In modern systems, people accept that JSON schemas and protocol contracts are architecture. In DOS-era workflows, plain text command output played that same role. If your automation consumed command output, formatting &lt;em&gt;was&lt;/em&gt; behavior.&lt;/p&gt;
&lt;p&gt;Our internal profile locked one specific command shape:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DIR [drive:][path][filespec]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;default long listing&lt;/li&gt;
&lt;li&gt;no &lt;code&gt;/W&lt;/code&gt;, no &lt;code&gt;/B&lt;/code&gt;, no formatting switches&lt;/li&gt;
&lt;li&gt;fixed US date/time rendering (&lt;code&gt;MM-DD-YY&lt;/code&gt;, &lt;code&gt;h:mma&lt;/code&gt; / &lt;code&gt;h:mmp&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That scoping decision solved half the problem. We stopped pretending one parser should support every possible switch/locale and instead declared a strict operating envelope.&lt;/p&gt;
&lt;h2 id=&#34;a-canonical-listing-is-worth-hours-of-debugging&#34;&gt;A canonical listing is worth hours of debugging&lt;/h2&gt;
&lt;p&gt;The profile included a canonical example and we used it as a fixture:&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-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; Volume in drive C has no label
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; Volume Serial Number is 3F2A-19C0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; Directory of C:\RETROLAB
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AUTOEXEC BAT      1024 03-09-96  9:40a
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;BIN              &amp;lt;DIR&amp;gt; 03-08-96  4:15p
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;DOCS             &amp;lt;DIR&amp;gt; 03-07-96 11:02a
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;README   TXT       512 03-09-96 10:20a
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SRC              &amp;lt;DIR&amp;gt; 03-07-96 11:04a
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;TOOLS    EXE     49152 03-09-96 10:21a
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       3 File(s)      50,688 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       3 Dir(s)  14,327,808 bytes free&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;Why include this in a spec? Because examples settle debates that prose cannot. When two engineers disagree, the fixture wins.&lt;/p&gt;
&lt;h2 id=&#34;the-38-column-row-discipline&#34;&gt;The 38-column row discipline&lt;/h2&gt;
&lt;p&gt;The core entry template was fixed-width:&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;/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;%-8s %-3s  %8s %8s %6s&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;That yields exactly 38 columns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;columns &lt;code&gt;1..8&lt;/code&gt;: basename (left-aligned)&lt;/li&gt;
&lt;li&gt;column &lt;code&gt;9&lt;/code&gt;: space&lt;/li&gt;
&lt;li&gt;columns &lt;code&gt;10..12&lt;/code&gt;: extension (left-aligned)&lt;/li&gt;
&lt;li&gt;columns &lt;code&gt;13..14&lt;/code&gt;: spaces&lt;/li&gt;
&lt;li&gt;columns &lt;code&gt;15..22&lt;/code&gt;: size-or-dir (right-aligned)&lt;/li&gt;
&lt;li&gt;column &lt;code&gt;23&lt;/code&gt;: space&lt;/li&gt;
&lt;li&gt;columns &lt;code&gt;24..31&lt;/code&gt;: date&lt;/li&gt;
&lt;li&gt;column &lt;code&gt;32&lt;/code&gt;: space&lt;/li&gt;
&lt;li&gt;columns &lt;code&gt;33..38&lt;/code&gt;: time (right-aligned)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you adopt positional parsing instead of regex guesswork, &lt;code&gt;DIR&lt;/code&gt; lines become boring in the best way.&lt;/p&gt;
&lt;h2 id=&#34;why-this-works-even-on-noisy-nights&#34;&gt;Why this works even on noisy nights&lt;/h2&gt;
&lt;p&gt;Fixed-width parsing has practical advantages under pressure:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;no locale-sensitive token splitting for date/time columns&lt;/li&gt;
&lt;li&gt;no ambiguity between &lt;code&gt;&amp;lt;DIR&amp;gt;&lt;/code&gt; and size values&lt;/li&gt;
&lt;li&gt;deterministic handling of one-digit vs two-digit hour&lt;/li&gt;
&lt;li&gt;easy visual validation during manual triage&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At 01:12, when you are diffing listings by eye and caffeine alone, &amp;ldquo;column 15 starts the size field&amp;rdquo; is operational mercy.&lt;/p&gt;
&lt;h2 id=&#34;header-and-footer-are-part-of-the-protocol&#34;&gt;Header and footer are part of the protocol&lt;/h2&gt;
&lt;p&gt;Many parsers only parse entry rows and ignore header/footer. That is a missed opportunity.&lt;/p&gt;
&lt;p&gt;Our profile explicitly fixed header sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;volume label line (&lt;code&gt;is &amp;lt;LABEL&amp;gt;&lt;/code&gt; or &lt;code&gt;has no label&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;serial line (&lt;code&gt;XXXX-XXXX&lt;/code&gt;, uppercase hex)&lt;/li&gt;
&lt;li&gt;blank line&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Directory of &amp;lt;PATH&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;blank line&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And footer sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;file totals: &lt;code&gt;%8u File(s) %11s bytes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;dir/free totals: &lt;code&gt;%8u Dir(s) %11s bytes free&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Those two footer lines are not decoration. They are integrity checks. If parsed file count says 127 and footer says 126, stop and investigate before touching production disks.&lt;/p&gt;
&lt;h2 id=&#34;parsing-algorithm-we-actually-trusted&#34;&gt;Parsing algorithm we actually trusted&lt;/h2&gt;
&lt;p&gt;This is the skeleton we converged on in Turbo Pascal style:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;type
  TDirEntry = record
    BaseName: string[8];
    Ext: string[3];
    IsDir: Boolean;
    SizeBytes: LongInt;
    DateText: string[8]; { MM-DD-YY }
    TimeText: string[6]; { right-aligned h:mma/h:mmp }
  end;

function TrimRight(const S: string): string;
var
  I: Integer;
begin
  I := Length(S);
  while (I &amp;gt; 0) and (S[I] = &amp;#39; &amp;#39;) do Dec(I);
  TrimRight := Copy(S, 1, I);
end;

function ParseEntryLine(const L: string; var E: TDirEntry): Boolean;
var
  NameField, ExtField, SizeField, DateField, TimeField: string;
  Code: Integer;
begin
  ParseEntryLine := False;
  if Length(L) &amp;lt; 38 then Exit;

  NameField := Copy(L, 1, 8);
  ExtField  := Copy(L, 10, 3);
  SizeField := Copy(L, 15, 8);
  DateField := Copy(L, 24, 8);
  TimeField := Copy(L, 33, 6);

  E.BaseName := TrimRight(NameField);
  E.Ext      := TrimRight(ExtField);
  E.DateText := DateField;
  E.TimeText := TimeField;

  if TrimRight(SizeField) = &amp;#39;&amp;lt;DIR&amp;gt;&amp;#39; then
  begin
    E.IsDir := True;
    E.SizeBytes := 0;
  end
  else
  begin
    E.IsDir := False;
    Val(TrimRight(SizeField), E.SizeBytes, Code);
    if Code &amp;lt;&amp;gt; 0 then Exit;
  end;

  ParseEntryLine := True;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This parser is intentionally plain. No hidden assumptions, no dynamic heuristics, no &amp;ldquo;best effort.&amp;rdquo; It either matches the profile or fails loudly.&lt;/p&gt;
&lt;h2 id=&#34;edge-cases-that-must-be-explicit&#34;&gt;Edge cases that must be explicit&lt;/h2&gt;
&lt;p&gt;The spec was strict about awkward but common cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;extensionless files: extension field is blank (three spaces in raw row)&lt;/li&gt;
&lt;li&gt;short names/exts: right-padding in fixed fields&lt;/li&gt;
&lt;li&gt;directories always use &lt;code&gt;&amp;lt;DIR&amp;gt;&lt;/code&gt; in size field&lt;/li&gt;
&lt;li&gt;if value exceeds width, allow rightward overflow; never truncate data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The overflow rule is subtle and important. Truncation creates false data, and false data is worse than ugly formatting.&lt;/p&gt;
&lt;h2 id=&#34;counting-bytes-grouped-vs-ungrouped-is-not-random&#34;&gt;Counting bytes: grouped vs ungrouped is not random&lt;/h2&gt;
&lt;p&gt;A detail teams often forget:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;entry &lt;code&gt;SIZE_OR_DIR&lt;/code&gt; file size is decimal without grouping&lt;/li&gt;
&lt;li&gt;footer byte totals are grouped with US commas in this profile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That split looks cosmetic until a parser accidentally strips commas in one place but not the other. If totals are part of your acceptance gate, normalize once and test it with fixtures.&lt;/p&gt;
&lt;h2 id=&#34;the-fictional-incident-that-made-it-real&#34;&gt;The fictional incident that made it real&lt;/h2&gt;
&lt;p&gt;At 02:07 in our story, we finally had a clean parse on machine A. We ran the same process on machine B, then compared manifests. Everything looked perfect except one tiny mismatch: file count agreed, byte count differed by 1,024.&lt;/p&gt;
&lt;p&gt;Old us would have guessed corruption and started copying disks again.&lt;/p&gt;
&lt;p&gt;Spec-driven us inspected footer math first, then entry parse, then source listing capture. The issue was not corruption. One listing had accidentally included a generated staging file from a side directory because the operator typed a wildcard path incorrectly.&lt;/p&gt;
&lt;p&gt;The deterministic header (&lt;code&gt;Directory of ...&lt;/code&gt;) and footer checks caught it in minutes.&lt;/p&gt;
&lt;p&gt;No drama. Just protocol discipline.&lt;/p&gt;
&lt;h2 id=&#34;what-this-teaches-beyond-dos&#34;&gt;What this teaches beyond DOS&lt;/h2&gt;
&lt;p&gt;The strongest lesson is not &amp;ldquo;DOS output is neat.&amp;rdquo; The lesson is operational:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;any text output consumed by tools should be treated as a contract&lt;/li&gt;
&lt;li&gt;contracts need explicit scope and out-of-scope declarations&lt;/li&gt;
&lt;li&gt;examples + field widths + sequence rules beat vague descriptions&lt;/li&gt;
&lt;li&gt;integrity lines (counts/totals) should be first-class validation points&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That mindset scales from floppy-era rebuild scripts to modern CI logs and telemetry processors.&lt;/p&gt;
&lt;h2 id=&#34;implementation-checklist-for-your-own-parser&#34;&gt;Implementation checklist for your own parser&lt;/h2&gt;
&lt;p&gt;If you want a stable implementation from this profile:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;enforce command profile (no unsupported switches)&lt;/li&gt;
&lt;li&gt;parse header in strict order&lt;/li&gt;
&lt;li&gt;parse entry rows by fixed columns, not token split&lt;/li&gt;
&lt;li&gt;parse footer totals and cross-check with computed values&lt;/li&gt;
&lt;li&gt;fail explicitly on profile deviation&lt;/li&gt;
&lt;li&gt;keep canonical fixture listings in version control&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This gives you deterministic behavior and debuggable failures.&lt;/p&gt;
&lt;h2 id=&#34;closing-scene&#34;&gt;Closing scene&lt;/h2&gt;
&lt;p&gt;At 03:18 we printed two manifests, one from recovered media and one from archive baseline, and compared them line by line. For the first time that night, we trusted the result.&lt;/p&gt;
&lt;p&gt;Not because the room got quieter.&lt;br&gt;
Not because the disks got newer.&lt;br&gt;
Because the contract got clearer.&lt;/p&gt;
&lt;p&gt;The old DOS prompt did what old prompts always do: it reflected our discipline back at us.&lt;/p&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/batch-file-wizardry/&#34;&gt;Batch File Wizardry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/config-sys-as-architecture/&#34;&gt;CONFIG.SYS as Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/interrupts-as-user-interface/&#34;&gt;Interrupts as User Interface&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>VFAT to 8.3: The Shortname Rules Behind the Curtain</title>
      <link>https://turbovision.in6-addr.net/retro/dos/vfat-to-8dot3-the-shortname-rules-behind-the-curtain/</link>
      <pubDate>Tue, 10 Mar 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Tue, 10 Mar 2026 00:00:00 +0000</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/vfat-to-8dot3-the-shortname-rules-behind-the-curtain/</guid>
      <description>&lt;p&gt;The second story begins with a floppy label that looked harmless:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RELEASE_NOTES_FINAL_REALLY_FINAL.TXT&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;By itself, that filename is only mildly annoying. Inside a mixed DOS/Windows pipeline in 1990s tooling, it can become a release blocker.&lt;/p&gt;
&lt;p&gt;Our fictional team learned this in one long weekend. The packager ran on a VFAT-capable machine. The installer verifier ran in a strict DOS context. The build ledger expected 8.3 aliases. Nobody had documented the shortname translation rules completely. Everybody thought they &amp;ldquo;basically knew&amp;rdquo; them.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Basically&amp;rdquo; lasted until the audit script flagged twelve mismatches that were all technically valid and operationally catastrophic.&lt;/p&gt;
&lt;p&gt;This article is the deep dive we wish we had then: how long names become 8.3 aliases, how collisions are resolved, and how to build deterministic tooling around those rules.&lt;/p&gt;
&lt;h2 id=&#34;first-principle-translate-per-path-component&#34;&gt;First principle: translate per path component&lt;/h2&gt;
&lt;p&gt;The most important rule is easy to miss:&lt;/p&gt;
&lt;p&gt;Translation happens per single path component, not on the full path string.&lt;/p&gt;
&lt;p&gt;That means each directory name and final file name is handled independently. If you normalize the entire path in one pass, you will eventually generate aliases that cannot exist in real directory contexts.&lt;/p&gt;
&lt;p&gt;In practical terms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C:\SRC\Very Long Directory\My Program Source.pas&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;is translated component-by-component, each with its own collision scope&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That &amp;ldquo;collision scope&amp;rdquo; phrase matters. Uniqueness is enforced within a directory, not globally across the volume.&lt;/p&gt;
&lt;h2 id=&#34;fast-path-already-legal-83-names-stay-as-is&#34;&gt;Fast path: already legal 8.3 names stay as-is&lt;/h2&gt;
&lt;p&gt;If the input is already a legal short name after OEM uppercase normalization, use that 8.3 form directly (uppercase).&lt;/p&gt;
&lt;p&gt;This avoids unnecessary alias churn and preserves operator expectations. A file named &lt;code&gt;CONFIG.SYS&lt;/code&gt; should not become something novel just because your algorithm always builds &lt;code&gt;FIRST6~1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Teams that skip this rule create avoidable incompatibilities.&lt;/p&gt;
&lt;h2 id=&#34;when-alias-generation-is-required&#34;&gt;When alias generation is required&lt;/h2&gt;
&lt;p&gt;If the name is not already legal 8.3, generate alias candidates using strict steps.&lt;/p&gt;
&lt;p&gt;The baseline candidate pattern is:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;FIRST6~1.EXT&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FIRST6&lt;/code&gt; is normalized/truncated basename prefix&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~1&lt;/code&gt; is initial numeric tail&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.EXT&lt;/code&gt; is extension if one exists, truncated to max 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No extension? Then no trailing dot/extension segment.&lt;/p&gt;
&lt;h2 id=&#34;dot-handling-is-where-most-bugs-hide&#34;&gt;Dot handling is where most bugs hide&lt;/h2&gt;
&lt;p&gt;Real filenames can contain multiple dots, trailing dots, and decorative punctuation. The rules must be explicit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;skip leading &lt;code&gt;.&lt;/code&gt; characters&lt;/li&gt;
&lt;li&gt;allow only one basename/extension separator in 8.3&lt;/li&gt;
&lt;li&gt;prefer the last dot that has valid non-space characters after it&lt;/li&gt;
&lt;li&gt;if name ends with a dot, ignore that trailing dot and use a previous valid dot if present&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the difference between deterministic behavior and parser folklore.&lt;/p&gt;
&lt;p&gt;Example intuition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;report.final.v3.txt&lt;/code&gt; -&amp;gt; extension source is last meaningful dot before &lt;code&gt;txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;archive.&lt;/code&gt; -&amp;gt; trailing dot is ignored; extension may end up empty&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;character-legality-and-normalization&#34;&gt;Character legality and normalization&lt;/h2&gt;
&lt;p&gt;Normalization from the spec includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;remove spaces and extra dots&lt;/li&gt;
&lt;li&gt;uppercase letters using active OEM code page semantics&lt;/li&gt;
&lt;li&gt;drop characters that are not representable/legal for short names&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Disallowed characters include control chars and:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;quot; * + , / : ; &amp;lt; = &amp;gt; ? [ \ ] |&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A critical note from the rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft-documented NT behavior: &lt;code&gt;[ ] + = , : ;&lt;/code&gt; are replaced with &lt;code&gt;_&lt;/code&gt; during short-name generation&lt;/li&gt;
&lt;li&gt;other illegal/superfluous characters are removed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your toolchain mixes &amp;ldquo;replace&amp;rdquo; and &amp;ldquo;remove&amp;rdquo; without policy, you will drift from expected aliases.&lt;/p&gt;
&lt;h2 id=&#34;collision-handling-is-an-algorithm-not-a-guess&#34;&gt;Collision handling is an algorithm, not a guess&lt;/h2&gt;
&lt;p&gt;The collision rule set is precise:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;try &lt;code&gt;~1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;if occupied, try &lt;code&gt;~2&lt;/code&gt;, &lt;code&gt;~3&lt;/code&gt;, &amp;hellip;&lt;/li&gt;
&lt;li&gt;as tail digits grow, shrink basename prefix so total basename+tail stays within 8 chars&lt;/li&gt;
&lt;li&gt;continue until unique in the directory&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That means &lt;code&gt;~10&lt;/code&gt; and &lt;code&gt;~100&lt;/code&gt; are not formatting quirks. They force basename compaction decisions.&lt;/p&gt;
&lt;p&gt;A common implementation failure is forgetting to shrink prefix when suffix width grows. The result is invalid aliases or silent truncation.&lt;/p&gt;
&lt;h2 id=&#34;a-deterministic-translator-skeleton&#34;&gt;A deterministic translator skeleton&lt;/h2&gt;
&lt;p&gt;The following Pascal-style pseudocode keeps policy explicit:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;function MakeShortAlias(const LongName: string; const Existing: TStringSet): string;
var
  BaseRaw, ExtRaw, BaseNorm, ExtNorm: string;
  Tail, PrefixLen: Integer;
  Candidate: string;
begin
  SplitUsingDotRules(LongName, BaseRaw, ExtRaw);   { skip leading dots, last valid dot logic }
  BaseNorm := NormalizeBase(BaseRaw);              { remove spaces/extra dots, uppercase, legality policy }
  ExtNorm  := NormalizeExt(ExtRaw);                { uppercase, legality policy, truncate to 3 }

  if IsLegal83(BaseNorm, ExtNorm) and (not Existing.Contains(Compose83(BaseNorm, ExtNorm))) then
  begin
    MakeShortAlias := Compose83(BaseNorm, ExtNorm);
    Exit;
  end;

  Tail := 1;
  repeat
    PrefixLen := 8 - (1 + Length(IntToStr(Tail))); { room for &amp;#34;~&amp;#34; + digits }
    if PrefixLen &amp;lt; 1 then PrefixLen := 1;
    Candidate := Copy(BaseNorm, 1, PrefixLen) + &amp;#39;~&amp;#39; + IntToStr(Tail);
    Candidate := Compose83(Candidate, ExtNorm);
    Inc(Tail);
  until not Existing.Contains(Candidate);

  MakeShortAlias := Candidate;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This intentionally leaves &lt;code&gt;NormalizeBase&lt;/code&gt;, &lt;code&gt;NormalizeExt&lt;/code&gt;, and &lt;code&gt;SplitUsingDotRules&lt;/code&gt; as separate units so policy stays testable.&lt;/p&gt;
&lt;h2 id=&#34;table-driven-tests-beat-intuition&#34;&gt;Table-driven tests beat intuition&lt;/h2&gt;
&lt;p&gt;Our fictional team fixed its pipeline by building a test corpus, not by debating memory:&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;/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;Input Component                         Expected Shape
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;--------------------------------------  ------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;README.TXT                              README.TXT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;very long filename.txt                  VERYLO~1.TXT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;archive.final.build.log                 ARCHIV~1.LOG
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;...hiddenprofile                        HIDDEN~1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;name with spaces.and.dots...cfg         NAMEWI~1.CFG&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;The exact alias strings can vary with existing collisions and code-page/legality policy details, but the algorithmic behavior should not vary.&lt;/p&gt;
&lt;h2 id=&#34;why-this-matters-in-operational-pipelines&#34;&gt;Why this matters in operational pipelines&lt;/h2&gt;
&lt;p&gt;Shortname translation touches many workflows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;installer scripts that reference legacy names&lt;/li&gt;
&lt;li&gt;backup/restore verification against manifests&lt;/li&gt;
&lt;li&gt;cross-tool compatibility between VFAT-aware and strict 8.3 utilities&lt;/li&gt;
&lt;li&gt;reproducible release artifacts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If alias generation is non-deterministic, two developers can build &amp;ldquo;same version&amp;rdquo; media with different effective filenames.&lt;/p&gt;
&lt;p&gt;That is a release-management nightmare.&lt;/p&gt;
&lt;h2 id=&#34;the-fictional-incident-response&#34;&gt;The fictional incident response&lt;/h2&gt;
&lt;p&gt;In our story, the break happened during a Friday packaging run. By Saturday morning, three teams had three conflicting explanations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;the verifier is wrong&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Windows generated weird aliases&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;someone copied files manually&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By Saturday afternoon, a tiny deterministic translator plus collision-aware tests cut through all three theories. The verifier was correct, alias generation differed between tools, and manual copies had introduced namespace collisions in one directory.&lt;/p&gt;
&lt;p&gt;Nobody needed blame. We needed rules.&lt;/p&gt;
&lt;h2 id=&#34;subtle-rule-legality-depends-on-oem-code-page&#34;&gt;Subtle rule: legality depends on OEM code page&lt;/h2&gt;
&lt;p&gt;One more important caveat from the spec:&lt;/p&gt;
&lt;p&gt;Uppercasing and character validity are evaluated in active OEM code page context.&lt;/p&gt;
&lt;p&gt;That means &amp;ldquo;works on my machine&amp;rdquo; can still fail if code-page assumptions differ. For strict reproducibility, pin the environment and test corpus together.&lt;/p&gt;
&lt;h2 id=&#34;practical-implementation-checklist&#34;&gt;Practical implementation checklist&lt;/h2&gt;
&lt;p&gt;For a robust translator:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;process one path component at a time&lt;/li&gt;
&lt;li&gt;implement legal-8.3 fast path first&lt;/li&gt;
&lt;li&gt;codify dot-selection/trailing-dot behavior exactly&lt;/li&gt;
&lt;li&gt;separate remove-vs-replace character policy clearly&lt;/li&gt;
&lt;li&gt;enforce extension max length 3&lt;/li&gt;
&lt;li&gt;implement collision tail growth with dynamic prefix shrink&lt;/li&gt;
&lt;li&gt;ship fixture tests with occupied-directory scenarios&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That last point is non-negotiable. Most alias bugs only appear under collision pressure.&lt;/p&gt;
&lt;h2 id=&#34;closing-scene&#34;&gt;Closing scene&lt;/h2&gt;
&lt;p&gt;Our weekend story ends around 01:03 on Sunday. The final verification pass prints green across every directory. The whiteboard still looks chaotic. The room still smells like old plastic and instant coffee. But now the behavior is explainable.&lt;/p&gt;
&lt;p&gt;Long names can still be expressive. Short names can still be strict. The bridge between them does not need magic. It needs documented rules and testable translation.&lt;/p&gt;
&lt;p&gt;In DOS-era engineering, that is usually the whole game: reduce mystery, increase repeatability, and let simple tools carry serious work.&lt;/p&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/deterministic-dir-output-as-an-operational-contract/&#34;&gt;Deterministic DIR Output as an Operational Contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/batch-file-wizardry/&#34;&gt;Batch File Wizardry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-units-as-architecture/&#34;&gt;Turbo Pascal Units as Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>C:\ After Midnight: A DOS Chronicle</title>
      <link>https://turbovision.in6-addr.net/retro/dos/c-after-midnight-a-dos-chronicle/</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/c-after-midnight-a-dos-chronicle/</guid>
      <description>&lt;p&gt;There is a particular blue that only old screens know how to make.
Not sky blue, not electric blue, not any brand color from modern design systems.
It is the blue of waiting, the blue of discipline, the blue of possibility.
It is the blue that appears when a machine, after clearing its throat with a POST beep, hands you a bare prompt and says: now it is your turn.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C:\&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;No dock, no notifications, no assistant bubble, no pretense of helping you think.
Only an invitation and a challenge. The operating system has done almost nothing.
You must do the rest.&lt;/p&gt;
&lt;p&gt;This is not an article about nostalgia as decoration.
It is about a working world that existed inside limits so hard they became architecture.
A world where your startup sequence was a design document, your tools fit on a few floppies, your failures had names, and your victories often looked like reclaiming 37 kilobytes of conventional memory so a game or compiler could start.
It is also a story, because DOS was never just a technical environment.
It was a culture of rituals: boot rituals, backup rituals, anti-virus rituals, debugging rituals, and social rituals that happened in school labs, basements, bedrooms, and noisy clubs where people traded disks like rare books.&lt;/p&gt;
&lt;p&gt;So let us spend one long night there.
Let us walk into a fictional but faithful 1994 room that smells like warm plastic and printer paper.
Let us build and run a complete DOS life from dusk to dawn.
Every choice in this chronicle is plausible.
Most of them were common.
Some of them were mistakes.
All of them are true to the era.&lt;/p&gt;
&lt;h2 id=&#34;1842---the-room-before-boot&#34;&gt;18:42 - The Room Before Boot&lt;/h2&gt;
&lt;p&gt;The desk is too small for the machine, so the machine dominates.
A beige tower sits on the floor, wearing scratches and an &amp;ldquo;Intel Inside&amp;rdquo; sticker that has started to peel at one corner.
On top of the tower rests a second floppy box because the first one filled months ago.
A 14-inch CRT sits forward like a stubborn old TV.
Behind it, cables twist into an unplanned knot that no one wants to touch because everything still works, somehow.&lt;/p&gt;
&lt;p&gt;The keyboard is heavy enough to qualify as carpentry.
Its space bar has a polished shine at the center where years of thumbs erased texture.
The mouse is optional, often unplugged, because many tasks are faster from keys alone.
To the right: a stack of 3.5-inch disks labeled in pen.
Some labels are clear: &amp;ldquo;TP7&amp;rdquo;, &amp;ldquo;NORTON&amp;rdquo;, &amp;ldquo;PKZIP&amp;rdquo;, &amp;ldquo;DOOM WADS&amp;rdquo;.
Some are warnings: &amp;ldquo;DO NOT FORMAT&amp;rdquo;, &amp;ldquo;GOOD BACKUP&amp;rdquo;, &amp;ldquo;MAYBE VIRUS&amp;rdquo;.
To the left: a notebook with IRQ tables, command aliases, half-finished phone numbers for BBS lines, and hand-drawn flowcharts for batch menus.&lt;/p&gt;
&lt;p&gt;The machine itself is a practical compromise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;486DX2/66&lt;/li&gt;
&lt;li&gt;8 MB RAM&lt;/li&gt;
&lt;li&gt;420 MB IDE hard drive&lt;/li&gt;
&lt;li&gt;Sound Blaster 16 clone&lt;/li&gt;
&lt;li&gt;SVGA card with 1 MB VRAM&lt;/li&gt;
&lt;li&gt;2x CD-ROM that reads when it feels respected&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing here is top-tier for magazines, but it is elite for doing real work.
This system can compile, dial, play, and occasionally multitask if treated carefully.
It can also punish impatience instantly.&lt;/p&gt;
&lt;p&gt;You sit down.
You press power.&lt;/p&gt;
&lt;h2 id=&#34;1843---the-beep-the-count-the-oath&#34;&gt;18:43 - The Beep, the Count, the Oath&lt;/h2&gt;
&lt;p&gt;Fans spin, drives click, and the BIOS begins its ceremony.
Memory counts upward in white text.
This number matters because it is the first confirmation that the machine woke up with all its limbs attached.
Any stutter means a module might be loose.
Any weird symbol means deeper trouble.
Any silence from the speaker means fear.&lt;/p&gt;
&lt;p&gt;Then the beep arrives.
One short beep: the civil peace of hardware has been declared.
A double or triple pattern would mean war.
You learn these codes the way sailors learn cloud shapes.&lt;/p&gt;
&lt;p&gt;IDE detection takes a breath.
The hard disk appears.
The floppy controller appears.
Sometimes the CD-ROM hangs here if the cable is old or the moon is wrong.
Tonight it passes.&lt;/p&gt;
&lt;p&gt;The bootloader takes over.
DOS emerges.
No loading animation.
No marketing.
Just text and trust.&lt;/p&gt;
&lt;p&gt;Before anything else, you watch startup lines for anomalies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Did HIMEM.SYS load?&lt;/li&gt;
&lt;li&gt;Did EMM386 complain?&lt;/li&gt;
&lt;li&gt;Did mouse.com detect hardware?&lt;/li&gt;
&lt;li&gt;Did MSCDEX hook the CD drive?&lt;/li&gt;
&lt;li&gt;Did SMARTDRV report cache enabled?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every message is operational telemetry.
If one line changes unexpectedly, your evening plans might collapse.
A failed memory manager means no game.
A failed CD extension means no install.
A failed sound driver means a silent night, and in DOS a silent night is not peaceful, it is broken.&lt;/p&gt;
&lt;p&gt;The prompt finally settles.
You are in.
And the first thing you do is not launch software.
You verify your environment.&lt;/p&gt;
&lt;h2 id=&#34;1847---configsys-constitution-of-a-small-republic&#34;&gt;18:47 - CONFIG.SYS, Constitution of a Small Republic&lt;/h2&gt;
&lt;p&gt;In DOS, policy is not hidden in control panels.
Policy lives in startup files.
&lt;code&gt;CONFIG.SYS&lt;/code&gt; is constitutional law: memory managers, file handles, buffers, shell behavior, and boot menus if you are ambitious.
One bad line can make the system unusable.
One smart line can unlock impossible combinations.&lt;/p&gt;
&lt;p&gt;Tonight&amp;rsquo;s &lt;code&gt;CONFIG.SYS&lt;/code&gt; is the result of months of tuning:&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;/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-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;DOS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;HIGH,UMB&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;na&#34;&gt;DEVICE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;C:\DOS\HIMEM.SYS /TESTMEM:OFF&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;na&#34;&gt;DEVICE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;C:\DOS\EMM386.EXE NOEMS I=B000-B7FF&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;na&#34;&gt;FILES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;40&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;na&#34;&gt;BUFFERS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;25&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;na&#34;&gt;LASTDRIVE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Z&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;na&#34;&gt;STACKS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;9,256&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;na&#34;&gt;SHELL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;C:\DOS\COMMAND.COM C:\DOS\ /E:1024 /P&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;na&#34;&gt;DEVICEHIGH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;C:\DOS\SETVER.EXE&lt;/span&gt;&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;Nothing here is accidental.
&lt;code&gt;DOS=HIGH,UMB&lt;/code&gt; pushes DOS itself into high memory and opens upper memory blocks.
&lt;code&gt;NOEMS&lt;/code&gt; is a strategic choice because expanded memory support can cost conventional memory and not every program needs it.
&lt;code&gt;I=B000-B7FF&lt;/code&gt; reclaims monochrome text memory as usable UMB on compatible hardware.
&lt;code&gt;FILES&lt;/code&gt; and &lt;code&gt;BUFFERS&lt;/code&gt; are set just high enough to avoid common failures but not so high that memory leaks from your hands.
&lt;code&gt;SHELL&lt;/code&gt; extends environment size because big batch systems starve with tiny defaults.&lt;/p&gt;
&lt;p&gt;In modern systems, configuration often feels reversible, low stakes, almost playful.
In DOS, editing startup files is surgery under local anesthesia.
You save.
You reboot.
You read every line.
You compare free memory before and after.&lt;/p&gt;
&lt;p&gt;People who never lived in this environment often assume the difficulty was primitive.
It was not primitive.
It was explicit.
DOS showed consequences immediately.
That is harder and better.&lt;/p&gt;
&lt;h2 id=&#34;1902---autoexecbat-morning-ritual-in-script-form&#34;&gt;19:02 - AUTOEXEC.BAT, Morning Ritual in Script Form&lt;/h2&gt;
&lt;p&gt;If &lt;code&gt;CONFIG.SYS&lt;/code&gt; is law, &lt;code&gt;AUTOEXEC.BAT&lt;/code&gt; is routine.
This file choreographs the moment your system becomes yours.
It sets &lt;code&gt;PATH&lt;/code&gt;, initializes drivers, chooses prompt style, maybe launches a menu, maybe starts a TSR for keyboard layouts, maybe does ten things no GUI startup manager would dare expose.&lt;/p&gt;
&lt;p&gt;Tonight&amp;rsquo;s file begins simple:&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;/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;k&#34;&gt;PROMPT&lt;/span&gt; $P$G
&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;PATH&lt;/span&gt; C:\DOS;C:\UTIL;C:\TP\BIN
&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;SET&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;TEMP&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;C:\TEMP
&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;SET&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BLASTER&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;A220 I5 D1 H5 T6
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;LH C:\DOS\MSCDEX.EXE /D:MSCD001 /L:E
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;LH C:\MOUSE\MOUSE.COM
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;LH C:\DOS\SMARTDRV.EXE 2048&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;Then comes the menu system.
Not because menus are necessary, but because everyone eventually gets tired of typing long paths and forgetting switch combinations.
A good startup menu turns a machine into an instrument.&lt;/p&gt;
&lt;p&gt;Option 1: &amp;ldquo;Work&amp;rdquo; profile.
Loads editor helper TSRs, no sound extras, max conventional memory for compiler.&lt;/p&gt;
&lt;p&gt;Option 2: &amp;ldquo;Play&amp;rdquo; profile.
Loads joystick and sound helpers, reduced disk cache, game launcher.&lt;/p&gt;
&lt;p&gt;Option 3: &amp;ldquo;Clean&amp;rdquo; profile.
Minimal drivers, troubleshooting mode, used when something is broken and you need the smallest reproducible boot.&lt;/p&gt;
&lt;p&gt;This is DevOps, 1994 edition: reproducible runtime states encoded in batch files and discipline.
No YAML required.
No orchestration stack.
Just precise ordering and complete responsibility.&lt;/p&gt;
&lt;h2 id=&#34;1918---the-640k-myth-and-the-real-memory-war&#34;&gt;19:18 - The 640K Myth and the Real Memory War&lt;/h2&gt;
&lt;p&gt;People quote &amp;ldquo;640K ought to be enough for anyone&amp;rdquo; even though the attribution is dubious.
The quote survives because the number was real pain.
Conventional memory is the first 640 KB of address space where many DOS programs must live.
Everything competes for it: drivers, TSRs, command shell, environment block, and your application.&lt;/p&gt;
&lt;p&gt;A 1994 machine might have 8 MB or 16 MB total RAM, yet still fail with:
&amp;ldquo;Not enough memory to run this program.&amp;rdquo;
This sounds absurd until you learn memory classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Conventional memory (precious)&lt;/li&gt;
&lt;li&gt;Upper memory blocks (reclaimable if lucky)&lt;/li&gt;
&lt;li&gt;High memory area (small but useful)&lt;/li&gt;
&lt;li&gt;Extended memory (XMS, accessible via manager)&lt;/li&gt;
&lt;li&gt;Expanded memory (EMS, bank-switched emulation or hardware)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You become a cartographer.
You run &lt;code&gt;MEM /C /P&lt;/code&gt; and stare at address ranges like a city planner.
You ask hard questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why is CD-ROM support consuming this much?&lt;/li&gt;
&lt;li&gt;Can mouse driver move to UMB?&lt;/li&gt;
&lt;li&gt;Is SMARTDRV worth its footprint tonight?&lt;/li&gt;
&lt;li&gt;Does this game require EMS, or does EMS only hurt us?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Optimization is not abstract.
It is measured in single kilobytes and concrete tradeoffs.
Reclaiming 12 KB can be the difference between launching and failing.
Reclaiming 40 KB feels like finding a hidden room in your house.&lt;/p&gt;
&lt;p&gt;The lesson scales.
When resources are finite and visible, engineering skill sharpens.
You cannot hide inefficiency behind &amp;ldquo;just add more RAM.&amp;rdquo;
You have to understand what each component does.
DOS taught this brutally and effectively.&lt;/p&gt;
&lt;h2 id=&#34;1937---device-drivers-as-characters-in-a-drama&#34;&gt;19:37 - Device Drivers as Characters in a Drama&lt;/h2&gt;
&lt;p&gt;Every driver has personality.
Some are polite and tiny.
Some are loud and hungry.
Some lie about compatibility.&lt;/p&gt;
&lt;p&gt;Your mouse driver might report &amp;ldquo;v8.20 loaded&amp;rdquo; with cheerful certainty while occasionally freezing in one specific game.
Your CD-ROM driver might work only if loaded before a specific cache utility.
Your sound card initialization utility might insist on IRQ 7 while the printer port already has political claim to it.&lt;/p&gt;
&lt;p&gt;A mature DOS setup feels less like software installation and more like coalition government.
You negotiate resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IRQ lines&lt;/li&gt;
&lt;li&gt;DMA channels&lt;/li&gt;
&lt;li&gt;I/O addresses&lt;/li&gt;
&lt;li&gt;upper memory slots&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You keep a written table in a notebook because forgetting one assignment can cost hours.
The canonical line for Sound Blaster compatibility is sacred:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SET BLASTER=A220 I5 D1 H5 T6&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Change one number blindly and half your games lose voice or effects.
Worse: some keep running with wrong audio, so you debug by listening for missing explosions.&lt;/p&gt;
&lt;p&gt;What modern systems abstract away, DOS made audible.
Conflict had texture.
Misconfiguration had timbre.
When everything aligned, the first digital speech sample from a game intro sounded like victory.&lt;/p&gt;
&lt;h2 id=&#34;2005---building-a-launcher-worth-keeping&#34;&gt;20:05 - Building a Launcher Worth Keeping&lt;/h2&gt;
&lt;p&gt;Tonight&amp;rsquo;s major project is not a game and not a compiler.
It is a launcher: a better front door for everything else.
You start with &lt;code&gt;MENU.BAT&lt;/code&gt;, then split logic into modular files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M_BOOT.BAT&lt;/code&gt; for profile setup&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M_GAMES.BAT&lt;/code&gt; for game categories&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M_DEV.BAT&lt;/code&gt; for tools and compilers&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M_NET.BAT&lt;/code&gt; for modem and BBS utilities&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M_UTIL.BAT&lt;/code&gt; for diagnostics and backup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You draw the menu tree on paper first.
This matters.
Without a map, batch files become spaghetti faster than any modern scripting language.&lt;/p&gt;
&lt;p&gt;Core techniques:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CHOICE /C:12345 /N&lt;/code&gt; for deterministic input&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IF ERRORLEVEL&lt;/code&gt; checks in descending order&lt;/li&gt;
&lt;li&gt;temporary environment variables for context&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CALL&lt;/code&gt; to return from submenus&lt;/li&gt;
&lt;li&gt;a shared &lt;code&gt;CLS&lt;/code&gt; and header routine for consistency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You include guardrails:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;check whether expected directory exists before launch&lt;/li&gt;
&lt;li&gt;print useful error if executable missing&lt;/li&gt;
&lt;li&gt;return cleanly rather than dropping to random path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At 20:41, you have version one.
It is ugly.
It works.
It feels luxurious.&lt;/p&gt;
&lt;p&gt;A modern reader may smile at this effort for &amp;ldquo;just a menu.&amp;rdquo;
That reaction misses the point.
Interface is leverage.
A good launcher saves friction every day.
In DOS, where every command is explicit, reducing friction means preserving focus.&lt;/p&gt;
&lt;h2 id=&#34;2058---floppy-disks-and-the-economy-of-scarcity&#34;&gt;20:58 - Floppy Disks and the Economy of Scarcity&lt;/h2&gt;
&lt;p&gt;Storage in DOS culture has sociology.
You do not merely &amp;ldquo;save files.&amp;rdquo;
You classify, rotate, compress, duplicate, and label.
A 1.44 MB floppy is tiny, but when it is all you have in your pocket, it becomes a strategy game.&lt;/p&gt;
&lt;p&gt;You carry disk sets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installer sets (Disk 1..n)&lt;/li&gt;
&lt;li&gt;Backup sets (A/B weekly rotation)&lt;/li&gt;
&lt;li&gt;Utility emergency disk (bootable, with key tools)&lt;/li&gt;
&lt;li&gt;Transfer disk (for school, friends, office)&lt;/li&gt;
&lt;li&gt;Risk disk (unknown files, quarantine first)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Compression is standard behavior, not optimization theater.
&lt;code&gt;PKZIP -ex&lt;/code&gt; is used because every kilobyte matters.
Self-extracting archives are convenience gold.
Multi-volume archives are often necessary and frequently cursed when one disk in the chain develops a bad sector.&lt;/p&gt;
&lt;p&gt;Disk labels are metadata.
Good labels include date, version, and source.
Bad labels say &amp;ldquo;stuff&amp;rdquo; and create archeology digs months later.&lt;/p&gt;
&lt;p&gt;Copy verification matters.
You learn to distrust successful completion messages from cheap media.
So you test restore paths.
You compute CRC when possible.
You attempt extraction before declaring backup complete.&lt;/p&gt;
&lt;p&gt;This discipline feels old-fashioned until you see modern teams lose data because they never practiced recovery.
DOS users practiced recovery constantly, because media failure was common and unforgiving.
Reliability was not promised; it was engineered by habit.&lt;/p&gt;
&lt;h2 id=&#34;2126---the-bbs-hour&#34;&gt;21:26 - The BBS Hour&lt;/h2&gt;
&lt;p&gt;At night the modem becomes a portal.
You launch terminal software, check initialization string, and listen.
Dial tone.
Digits.
Carrier negotiation song.
Static.
Then connection: maybe 2400, maybe 9600, maybe luck grants 14400.&lt;/p&gt;
&lt;p&gt;Bulletin board systems are part library, part arcade, part neighborhood.
Each board has personality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;strict sysop rules and curated files&lt;/li&gt;
&lt;li&gt;chaotic message bases with philosophical flame wars&lt;/li&gt;
&lt;li&gt;niche communities for one game, one language, one region&lt;/li&gt;
&lt;li&gt;elite boards with ratio systems and demanding etiquette&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You do not browse infinitely.
Phone bills are real constraints.
So you arrive with intent:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Upload contribution first (new utility, bugfix, walkthrough).&lt;/li&gt;
&lt;li&gt;Download target files using queued protocol.&lt;/li&gt;
&lt;li&gt;Read priority messages.&lt;/li&gt;
&lt;li&gt;Log off cleanly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Transfer protocols matter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XMODEM for compatibility&lt;/li&gt;
&lt;li&gt;YMODEM for batch&lt;/li&gt;
&lt;li&gt;ZMODEM for speed and resume convenience&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A failed transfer at 97 percent can ruin your mood for an hour.
A clean ZMODEM session feels like winning a race.&lt;/p&gt;
&lt;p&gt;BBS culture taught social engineering before that term became security jargon.
Reputation mattered.
You gained trust by contributing, documenting, and not uploading garbage.
You lost trust quickly by ignoring standards.
Moderation existed, but mostly through sysop judgment and local norms.
Communities were smaller, more accountable, and often surprisingly generous.&lt;/p&gt;
&lt;h2 id=&#34;2203---editors-compilers-and-the-craft-loop&#34;&gt;22:03 - Editors, Compilers, and the Craft Loop&lt;/h2&gt;
&lt;p&gt;Now the serious work begins: coding.
Tonight&amp;rsquo;s project is a small &amp;ldquo;ship log&amp;rdquo; program for a sci-fi tabletop campaign.
Requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;store captain name&lt;/li&gt;
&lt;li&gt;append mission entries&lt;/li&gt;
&lt;li&gt;show entries with timestamp&lt;/li&gt;
&lt;li&gt;export as text&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Turbo Pascal launches nearly instantly.
That speed changes behavior.
You iterate more because compile-run cycles are cheap.
You write one function, test immediately, adjust, repeat.&lt;/p&gt;
&lt;p&gt;The editor is not modern, but it is coherent.
Keyboard-first navigation.
Predictable menus.
No plugin maze.
No dependency download.
The machine&amp;rsquo;s whole attitude says: write code now.&lt;/p&gt;
&lt;p&gt;You draft data structures.
You remember fixed-size arrays before dynamic containers.
You choose records with clear field lengths because memory is budget.
You learn to think in layouts, not abstractions detached from cost.&lt;/p&gt;
&lt;p&gt;By 22:44 you hit a bug: timestamps show garbage in exported file.
Root cause: uninitialized variable in formatting routine.
Fix: explicit initialization and bound checks.
No framework catches this for you.
You catch it by reading your own code carefully and validating outputs.&lt;/p&gt;
&lt;p&gt;DOS development gave many people their first honest relationship with determinism.
Programs did exactly what you wrote, not what you intended.
That gap is where craftsmanship lives.&lt;/p&gt;
&lt;h2 id=&#34;2258---debugging-without-theater&#34;&gt;22:58 - Debugging Without Theater&lt;/h2&gt;
&lt;p&gt;There is a clean beauty in simple debugging tools.
No telemetry stack.
No cloud traces.
No billion-line logs.
Just targeted prints, careful reasoning, and binary search through code paths.&lt;/p&gt;
&lt;p&gt;Tonight you test file append behavior under stress.
You generate 500 entries, each with varying length.
Expected outcome before run:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no truncated records&lt;/li&gt;
&lt;li&gt;file size increases predictably&lt;/li&gt;
&lt;li&gt;UI list remains responsive&lt;/li&gt;
&lt;li&gt;no crash on boundary at max entries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Observed outcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;records above 255 chars truncate&lt;/li&gt;
&lt;li&gt;size increments mostly predictably but with occasional mismatch&lt;/li&gt;
&lt;li&gt;UI slows but survives&lt;/li&gt;
&lt;li&gt;boundary condition crashes on entry 501&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Difference analysis:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one-byte length assumption leaked from old helper routine&lt;/li&gt;
&lt;li&gt;boundary check uses &lt;code&gt;&amp;gt;&lt;/code&gt; where &lt;code&gt;&amp;gt;=&lt;/code&gt; was required&lt;/li&gt;
&lt;li&gt;mismatch due to newline handling inconsistency between display and export&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You fix each issue, rerun same test, compare against expected behavior again.
This discipline is timeless: predict, observe, explain difference, adjust.
DOS did not invent it, but DOS rewarded it fast.&lt;/p&gt;
&lt;p&gt;When toolchains are thin, your method matters more.
That is a gift disguised as inconvenience.&lt;/p&gt;
&lt;h2 id=&#34;2331---games-as-hardware-diagnostics&#34;&gt;23:31 - Games as Hardware Diagnostics&lt;/h2&gt;
&lt;p&gt;Around midnight, development pauses and diagnostics begin, disguised as fun.
A few game launches can tell you more about system health than many utilities.&lt;/p&gt;
&lt;p&gt;Game A checks memory layout sensitivity.
Game B checks sound card IRQ/DMA sanity.
Game C checks VGA mode compatibility.
Game D checks CD streaming and disk throughput.&lt;/p&gt;
&lt;p&gt;You keep a mental matrix:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If digital effects work but music fails, inspect MIDI config.&lt;/li&gt;
&lt;li&gt;If intro videos stutter, inspect cache and drive mode.&lt;/li&gt;
&lt;li&gt;If joystick drifts, recalibrate and verify gameport noise.&lt;/li&gt;
&lt;li&gt;If random crashes appear only in one title, suspect EMS/XMS setting mismatch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why old forum advice often started with &amp;ldquo;what games fail?&amp;rdquo;
Games were comprehensive integration tests for consumer PCs.
They touched timing, graphics, audio, input, memory, disk, and often copy-protection edge cases.&lt;/p&gt;
&lt;p&gt;Tonight one title locks after logo.
You troubleshoot:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run clean boot profile.&lt;/li&gt;
&lt;li&gt;Disable EMM386.&lt;/li&gt;
&lt;li&gt;Change sound IRQ from 5 to 7 in setup utility.&lt;/li&gt;
&lt;li&gt;Re-test.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It works on step 3.
Root cause: hidden conflict with network card TSR loaded in play profile.
You update documentation notebook accordingly.&lt;/p&gt;
&lt;p&gt;Modern systems can hide this complexity.
DOS made you model it.
That modeling skill transfers directly to contemporary incident response.&lt;/p&gt;
&lt;h2 id=&#34;0004---dot-matrix-midnight-and-the-sound-of-output&#34;&gt;00:04 - Dot Matrix Midnight and the Sound of Output&lt;/h2&gt;
&lt;p&gt;At 00:04, the house is quiet enough that printing feels illegal.
Yet you print anyway, because paper is still the best way to review long code and BBS message drafts.&lt;/p&gt;
&lt;p&gt;The dot matrix wakes like a factory machine:
tractor feed catches,
head moves with aggressive rhythm,
pins strike ribbon,
letters appear in a texture that looks more manufactured than drawn.&lt;/p&gt;
&lt;p&gt;Printing in DOS is deceptively simple.
&lt;code&gt;COPY FILE.TXT LPT1&lt;/code&gt; might be enough.
Until it is not.&lt;/p&gt;
&lt;p&gt;Common realities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;printer expects different control codes&lt;/li&gt;
&lt;li&gt;line endings cause ugly wrapping&lt;/li&gt;
&lt;li&gt;graphics mode drivers consume huge memory&lt;/li&gt;
&lt;li&gt;bidirectional cable quality affects reliability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You learn escape sequences for bold, condensed, reset.
You keep a tiny utility for form feed.
You clear stalled print jobs by power-cycling in exactly the right order.&lt;/p&gt;
&lt;p&gt;The printer is loud, yes, but also clarifying.
When output becomes physical, you read with different care.
Typos that survived on screen jump out on paper.
Overlong variable names and awkward menu copy suddenly offend.&lt;/p&gt;
&lt;p&gt;In a strange way, this analog detour improves digital quality.
DOS workflows were full of such loops: constrained media forcing deliberate review.&lt;/p&gt;
&lt;h2 id=&#34;0037---viruses-trust-and-street-level-security&#34;&gt;00:37 - Viruses, Trust, and Street-Level Security&lt;/h2&gt;
&lt;p&gt;Security in DOS culture is local, immediate, and personal.
Threats arrive on floppy disks, BBS downloads, and borrowed game collections.
There are no automatic background updates.
There is only your process.&lt;/p&gt;
&lt;p&gt;Typical defense ritual:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Boot from trusted clean floppy.&lt;/li&gt;
&lt;li&gt;Run scanner against suspect media.&lt;/li&gt;
&lt;li&gt;Inspect boot sectors.&lt;/li&gt;
&lt;li&gt;Copy only necessary files.&lt;/li&gt;
&lt;li&gt;Re-scan destination.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You maintain a &amp;ldquo;quarantine&amp;rdquo; directory and never execute unknown binaries directly from incoming disks.
You keep checksums for critical utilities.
You write-protect master install disks physically whenever possible.&lt;/p&gt;
&lt;p&gt;Social trust is part of security posture.
Files from known sysops carry more confidence.
Random archives with dramatic names do not.
Executable games with no documentation are suspicious.&lt;/p&gt;
&lt;p&gt;Many users learn the hard way after first infection:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;altered boot records&lt;/li&gt;
&lt;li&gt;strange memory residency&lt;/li&gt;
&lt;li&gt;disappearing files&lt;/li&gt;
&lt;li&gt;unexpected messages at startup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recovery is painful enough that habits change.
People who lived through this era often become very good at skeptical intake and layered backup.
When every machine is a kingdom with weak walls, you learn gatekeeping.&lt;/p&gt;
&lt;p&gt;DOS security was imperfect and often bypassed.
But it trained a mindset modern convenience sometimes erodes: assume nothing is safe by default.&lt;/p&gt;
&lt;h2 id=&#34;0103---the-aesthetic-of-plain-text&#34;&gt;01:03 - The Aesthetic of Plain Text&lt;/h2&gt;
&lt;p&gt;DOS taught an underrated design lesson: plain text scales astonishingly far.
Configuration, scripts, notes, source code, logs, to-do lists, and even mini databases often live as text.
Text is inspectable, diffable (even by eyeballing), compressible, and recoverable.&lt;/p&gt;
&lt;p&gt;Binary formats exist, of course, but text remains the backbone.
You can open a &lt;code&gt;.BAT&lt;/code&gt; in any editor.
You can parse your own logs with one-liners.
You can rescue important data from partially damaged files more often than with opaque binaries.&lt;/p&gt;
&lt;p&gt;Tonight you migrate your project notes from scattered files into one structured log:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TODO.TXT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BUGS.TXT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IDEAS.TXT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HARDWARE.TXT&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each file starts with date-prefixed entries.
No tooling dependency.
No schema migration.
No vendor lock.&lt;/p&gt;
&lt;p&gt;This is not anti-progress.
It is strategic minimalism.
When formats are simple, system longevity improves.
A file you wrote in 1994 can often still be read in 2026 without conversion pipelines.
That is remarkable durability.&lt;/p&gt;
&lt;p&gt;The modern web rediscovered this truth through markdown and plaintext knowledge bases.
DOS users had no choice, and therefore learned it deeply.&lt;/p&gt;
&lt;h2 id=&#34;0128---naming-paths-and-the-poetry-of-83&#34;&gt;01:28 - Naming, Paths, and the Poetry of 8.3&lt;/h2&gt;
&lt;p&gt;Filenames in classic DOS often follow 8.3 constraints:
up to eight characters, dot, three-character extension.
People mock it as primitive.
It is.
It is also a forcing function for concise naming.&lt;/p&gt;
&lt;p&gt;Conventions emerge:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;README.TXT&lt;/code&gt; for human orientation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INSTALL.BAT&lt;/code&gt; for setup entry&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CFG&lt;/code&gt; for config&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DOC&lt;/code&gt; for manuals&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PAS&lt;/code&gt; and &lt;code&gt;ASM&lt;/code&gt; for source&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You become intentional about directory hierarchy because deep nesting is painful and long names are unavailable.
A good tree might look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C:\WORK\SHIPLOG&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C:\GAMES\SIM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C:\UTIL\ARCHIVE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even with constraints, creativity leaks through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NITEBOOT.BAT&lt;/code&gt; for midnight profile&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FIXIRQ.BAT&lt;/code&gt; for emergency audio reset&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SAFECPY.BAT&lt;/code&gt; for verified copy with logging&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Limited naming can improve shared understanding.
A teammate opening your disk does not need a wiki to locate essentials.
Clarity lives in path design.&lt;/p&gt;
&lt;p&gt;In modern systems, we enjoy long names and Unicode.
That is good progress.
But the DOS lesson remains: name things so a tired human can navigate at 2 AM with no context.&lt;/p&gt;
&lt;h2 id=&#34;0154---a-small-disaster-and-a-better-backup-plan&#34;&gt;01:54 - A Small Disaster and a Better Backup Plan&lt;/h2&gt;
&lt;p&gt;No long DOS night is complete without a scare.
Tonight it comes from a hard disk click pattern you recognize and hate.
A utility write operation stalls.
Directory listing returns slowly.
Then one file shows corrupted size.&lt;/p&gt;
&lt;p&gt;Panic is natural.
Protocol is better.&lt;/p&gt;
&lt;p&gt;Immediate response:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stop all writes.&lt;/li&gt;
&lt;li&gt;Reboot from trusted floppy.&lt;/li&gt;
&lt;li&gt;Run disk check in read-only mindset first.&lt;/li&gt;
&lt;li&gt;Identify most critical files.&lt;/li&gt;
&lt;li&gt;Copy priority data to known-good media.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You lose one cache file and a temporary archive.
You save source code, notes, and configuration.
Damage is limited because weekly rotation backups existed.&lt;/p&gt;
&lt;p&gt;This event triggers policy change.
You redesign backup process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;daily incremental to floppy set (work files)&lt;/li&gt;
&lt;li&gt;weekly full archive split across labeled disks&lt;/li&gt;
&lt;li&gt;monthly &amp;ldquo;cold&amp;rdquo; backup stored away from desk&lt;/li&gt;
&lt;li&gt;quarterly restore drill to verify process actually works&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You also add &lt;code&gt;BACKLOG.TXT&lt;/code&gt; to log backup dates and outcomes.
Trust now comes from evidence, not intention.&lt;/p&gt;
&lt;p&gt;Modern cloud sync can create illusion of safety.
It helps, but it is not equivalent to tested restore paths.
The DOS era taught this because failure was loud and frequent.
Reliability is a practiced behavior, not a subscription feature.&lt;/p&gt;
&lt;h2 id=&#34;0221---multitasking-dreams-and-honest-limits&#34;&gt;02:21 - Multitasking Dreams and Honest Limits&lt;/h2&gt;
&lt;p&gt;By 1994, many users tasted GUI multitasking through Windows, OS/2, or DESQview.
Still, pure DOS sessions remained where speed and control mattered most.
People asked the same question we ask now in different form:
can I do everything at once?&lt;/p&gt;
&lt;p&gt;In DOS, the answer is mostly no, and that honesty is refreshing.
Foreground program owns the machine.
TSRs fake multitasking for narrow tasks: keyboard helpers, print spoolers, clipboards, pop-up calculators.
Beyond that, context switches are human, not scheduler-driven.&lt;/p&gt;
&lt;p&gt;This limitation changes behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You plan task order.&lt;/li&gt;
&lt;li&gt;You finish one operation before starting the next.&lt;/li&gt;
&lt;li&gt;You script repetitive work.&lt;/li&gt;
&lt;li&gt;You avoid background complexity unless necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Productivity becomes sequence design.
You think in pipelines:&lt;/p&gt;
&lt;p&gt;edit -&amp;gt; compile -&amp;gt; test -&amp;gt; package -&amp;gt; transfer.&lt;/p&gt;
&lt;p&gt;When every step is explicit, wasted motion becomes visible.
Many modern productivity problems are not missing features.
They are hidden sequence costs.
DOS users felt sequence costs constantly and therefore optimized habit.&lt;/p&gt;
&lt;p&gt;Constraint can be cognitive ergonomics.
Not always.
But often enough to be worth remembering.&lt;/p&gt;
&lt;h2 id=&#34;0246---hardware-surgery-at-night&#34;&gt;02:46 - Hardware Surgery at Night&lt;/h2&gt;
&lt;p&gt;At 02:46 you do the thing everyone swears not to do late at night: open the case.
Reason: intermittent audio pop that software fixes did not solve.&lt;/p&gt;
&lt;p&gt;Static precautions are improvised but sincere:
touch grounded metal,
avoid carpet shuffle,
move slowly.&lt;/p&gt;
&lt;p&gt;Inside, the machine is a geography lesson:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ribbon cables folded like paper roads&lt;/li&gt;
&lt;li&gt;ISA cards seated with uncertain confidence&lt;/li&gt;
&lt;li&gt;dust colonies around heatsink and fan&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You reseat the sound card.
You inspect jumper settings against your notebook.
You notice one jumper moved slightly off expected pins, probably from vibration over years.
You correct it, close case, reboot, test.&lt;/p&gt;
&lt;p&gt;Problem gone.&lt;/p&gt;
&lt;p&gt;This is not romantic.
It is practical literacy.
Users in this era often crossed boundaries between software and hardware because they had to.
That cross-layer awareness is rare now, and teams pay for its absence with slow diagnostics and tribal silos.&lt;/p&gt;
&lt;p&gt;When you physically touch the subsystem you configure, abstractions become real.
IRQ is no longer &amp;ldquo;some setting.&amp;rdquo;
It is a finite line negotiated by components you can point to.&lt;/p&gt;
&lt;h2 id=&#34;0312---the-long-build-and-the-quiet-concentration&#34;&gt;03:12 - The Long Build and the Quiet Concentration&lt;/h2&gt;
&lt;p&gt;The rest of the night is steady work.
No big events.
No drama.
Just compiles, tests, edits, and notes.
This is where craft actually happens.&lt;/p&gt;
&lt;p&gt;You refine the ship log tool:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add search by captain&lt;/li&gt;
&lt;li&gt;add compact list mode&lt;/li&gt;
&lt;li&gt;improve export formatting&lt;/li&gt;
&lt;li&gt;add command-line switches for batch usage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You write usage docs in plain text.
You include examples.
You include known limitations.
You include version history with dates.
Future-you will be grateful.&lt;/p&gt;
&lt;p&gt;By 03:58, version 0.9 feels stable.
You package distribution:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PKZIP SHIPLOG09.ZIP *.EXE *.TXT *.CFG&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then you test install in a clean directory from archive, exactly as another user would.
Expected outcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;unpack cleanly&lt;/li&gt;
&lt;li&gt;run without additional files&lt;/li&gt;
&lt;li&gt;generate default config if missing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Observed outcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;unpack cleanly&lt;/li&gt;
&lt;li&gt;startup fails if &lt;code&gt;TEMP&lt;/code&gt; variable undefined&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fix:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add fallback to current directory when &lt;code&gt;TEMP&lt;/code&gt; absent&lt;/li&gt;
&lt;li&gt;update docs&lt;/li&gt;
&lt;li&gt;repack as 0.9a&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That extra test saves your reputation later.
Most software quality wins come from boring verification, not heroic debugging.&lt;/p&gt;
&lt;h2 id=&#34;0417---why-this-era-made-strong-builders&#34;&gt;04:17 - Why This Era Made Strong Builders&lt;/h2&gt;
&lt;p&gt;It is tempting to read all this as old-tech cosplay.
That would be shallow.
The deeper value of DOS is pedagogical.
It forced visibility of system layers and cost models:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;startup order mattered&lt;/li&gt;
&lt;li&gt;resource allocation was finite and inspectable&lt;/li&gt;
&lt;li&gt;interfaces were simple but composable&lt;/li&gt;
&lt;li&gt;failure modes were direct and attributable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From this environment, people learned transferable habits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Observe before acting.&lt;/li&gt;
&lt;li&gt;Document assumptions.&lt;/li&gt;
&lt;li&gt;Build reproducible workflows.&lt;/li&gt;
&lt;li&gt;Test from clean states.&lt;/li&gt;
&lt;li&gt;Treat backup and recovery as first-class engineering.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Modern stacks are far more capable and complex.
Good.
But complexity without visibility can weaken operator intuition.
That is why retro practice still helps.
It is not about rejecting progress.
It is about training mental models on a system small enough to understand end to end.&lt;/p&gt;
&lt;p&gt;If you can reason about a DOS boot chain and memory map, you are better prepared to reason about container startup orders, dependency graphs, and runtime budgets today.
The scale changed.
The logic did not.&lt;/p&gt;
&lt;h2 id=&#34;0439---rebuilding-the-experience-in-2026&#34;&gt;04:39 - Rebuilding the Experience in 2026&lt;/h2&gt;
&lt;p&gt;Suppose you want this learning now, not as museum nostalgia but as active practice.
You can recreate a meaningful DOS environment in an evening.&lt;/p&gt;
&lt;p&gt;Practical approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use an emulator (DOSBox-X or PCem-class tools if you want lower-level authenticity).&lt;/li&gt;
&lt;li&gt;Install MS-DOS compatible environment (or FreeDOS for legal convenience).&lt;/li&gt;
&lt;li&gt;Build from scratch:
&lt;ul&gt;
&lt;li&gt;text editor&lt;/li&gt;
&lt;li&gt;archiver&lt;/li&gt;
&lt;li&gt;compiler/interpreter&lt;/li&gt;
&lt;li&gt;file manager&lt;/li&gt;
&lt;li&gt;diagnostics utilities&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Write your own &lt;code&gt;CONFIG.SYS&lt;/code&gt; and &lt;code&gt;AUTOEXEC.BAT&lt;/code&gt; rather than copying premade blobs.&lt;/li&gt;
&lt;li&gt;Keep a real notebook for IRQ/port/memory notes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Learning exercises worth doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reclaim conventional memory for a demanding app&lt;/li&gt;
&lt;li&gt;create boot menu profiles for different tasks&lt;/li&gt;
&lt;li&gt;script a full backup and verify restore&lt;/li&gt;
&lt;li&gt;build one useful command-line tool in Pascal, C, or assembly&lt;/li&gt;
&lt;li&gt;document and fix one intentional misconfiguration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Expected outcomes if done seriously:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stronger intuition for startup/runtime boundaries&lt;/li&gt;
&lt;li&gt;better troubleshooting sequence discipline&lt;/li&gt;
&lt;li&gt;improved empathy for low-resource systems&lt;/li&gt;
&lt;li&gt;renewed appreciation for explicit tooling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is not mandatory for modern development.
It is high-return training if you enjoy systems thinking.&lt;/p&gt;
&lt;h2 id=&#34;0503---dawn-prompt-and-continuity&#34;&gt;05:03 - Dawn, Prompt, and Continuity&lt;/h2&gt;
&lt;p&gt;The sky outside shifts from black to gray.
You have been awake through one complete cycle of your machine and your own attention.
Nothing in this room has gone viral.
No dashboard celebrated your streak.
No cloud service congratulated your retention.
Yet real progress happened:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a tuned boot environment&lt;/li&gt;
&lt;li&gt;a cleaner launcher&lt;/li&gt;
&lt;li&gt;a tested utility release&lt;/li&gt;
&lt;li&gt;documented fixes&lt;/li&gt;
&lt;li&gt;improved backup policy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You type one last command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DIR C:\WORK\SHIPLOG&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Files listed.
Dates updated.
Sizes plausible.
No surprises.&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C:\&amp;gt;EXIT&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Monitor clicks to black.
Room goes quiet except for fan spin-down.&lt;/p&gt;
&lt;p&gt;What remains is not merely data.
It is a learned posture:
respect constraints,
prefer clarity,
test assumptions,
document reality,
build tools that serve humans under pressure.&lt;/p&gt;
&lt;p&gt;That posture is timeless.
It worked on DOS.
It works now.&lt;/p&gt;
&lt;h2 id=&#34;appendix---midnight-recipes-from-the-notebook&#34;&gt;Appendix - Midnight Recipes from the Notebook&lt;/h2&gt;
&lt;p&gt;Because every DOS chronicle should end with practical scraps, here are compact recipes that earned permanent place in my notebook.&lt;/p&gt;
&lt;h3 id=&#34;1-fast-memory-sanity-check&#34;&gt;1) Fast memory sanity check&lt;/h3&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;/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;MEM /C /P
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;PAUSE&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;Use before and after startup edits.
Do not trust memory &amp;ldquo;feelings&amp;rdquo;; trust measured deltas.&lt;/p&gt;
&lt;h3 id=&#34;2-safer-copy-with-verification&#34;&gt;2) Safer copy with verification&lt;/h3&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;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&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;k&#34;&gt;IF&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;%1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;GOTO&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;usage&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;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;%2&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;GOTO&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;usage&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;COPY&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;%1&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;%2&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;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;FC /B &lt;span class=&#34;nv&#34;&gt;%1&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;%2&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;NUL
&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;ECHO&lt;/span&gt; VERIFIED: &lt;span class=&#34;nv&#34;&gt;%1&lt;/span&gt; -&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;%2&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;GOTO&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;end&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; COPY OR VERIFY 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;GOTO&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;end&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;usage&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; USAGE: SAFECPY source target
&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;end&lt;/span&gt;&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;Not elegant, but good enough to prevent silent corruption surprises.&lt;/p&gt;
&lt;h3 id=&#34;3-menu-pattern-that-never-betrays-you&#34;&gt;3) Menu pattern that never betrays you&lt;/h3&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;/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;nl&#34;&gt;menu&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;CLS&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; [1] Work
&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; [2] Games
&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; [3] Tools
&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; [4] Exit
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;CHOICE /C:1234 /N /M &lt;span class=&#34;s2&#34;&gt;&amp;#34;Select:&amp;#34;&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;ERRORLEVEL&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;GOTO&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;done&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;ERRORLEVEL&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;GOTO&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;tools&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;ERRORLEVEL&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;GOTO&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;games&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;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;work&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;GOTO&lt;/span&gt; &lt;span class=&#34;nl&#34;&gt;menu&lt;/span&gt;&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;Descending &lt;code&gt;ERRORLEVEL&lt;/code&gt; checks save hours of subtle bugs.&lt;/p&gt;
&lt;h3 id=&#34;4-packaging-checklist&#34;&gt;4) Packaging checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Build from clean boot profile.&lt;/li&gt;
&lt;li&gt;Delete temp artifacts.&lt;/li&gt;
&lt;li&gt;Zip binaries, docs, sample config.&lt;/li&gt;
&lt;li&gt;Extract into empty directory and run there.&lt;/li&gt;
&lt;li&gt;Confirm defaults for missing environment variables.&lt;/li&gt;
&lt;li&gt;Write changelog entry before upload.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A release is not complete when it compiles.
A release is complete when someone else can use it without guessing.&lt;/p&gt;
&lt;h3 id=&#34;5-two-golden-notes&#34;&gt;5) Two golden notes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;If it only works on your machine, it is not done.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;If you cannot restore it, you do not have it.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These notes survived every platform transition I have lived through.&lt;/p&gt;
&lt;h2 id=&#34;final-reflection&#34;&gt;Final Reflection&lt;/h2&gt;
&lt;p&gt;The DOS era is often described with a grin and a shrug: primitive, charming, inconvenient.
Those words are not wrong, but they are incomplete.
It was also rigorous, educative, and deeply empowering for anyone willing to understand the machine as a layered system instead of a magic appliance.&lt;/p&gt;
&lt;p&gt;When you stare at a plain prompt, there is nowhere to hide.
You either know what happens next, or you learn.
That directness is rare now.
It is worth preserving.&lt;/p&gt;
&lt;p&gt;So if you ever find yourself inside a retro setup at 2 AM, cursor blinking, no GUI in sight, do not treat it as reenactment.
Treat it as training.
Build something small.
Tune something real.
Break something recoverably.
Write down what happened.
Then do it again until cause and effect become instinct.&lt;/p&gt;
&lt;p&gt;The old blue screen will not flatter you.
It will teach you.&lt;/p&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/batch-file-wizardry/&#34;&gt;Batch File Wizardry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-in-2025/&#34;&gt;Writing Turbo Pascal in 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/hardware/restoring-a-286/&#34;&gt;Restoring an AT 286&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>CONFIG.SYS as Architecture</title>
      <link>https://turbovision.in6-addr.net/retro/dos/config-sys-as-architecture/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:14:20 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/config-sys-as-architecture/</guid>
      <description>&lt;p&gt;In DOS culture, &lt;code&gt;CONFIG.SYS&lt;/code&gt; is often remembered as a startup file full of cryptic lines. That memory is accurate and incomplete. In practice, &lt;code&gt;CONFIG.SYS&lt;/code&gt; was architecture: a compact declaration of runtime policy, resource allocation, compatibility strategy, and operational profile.&lt;/p&gt;
&lt;p&gt;Before your application loaded, your architecture was already making decisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;memory model and address space usage&lt;/li&gt;
&lt;li&gt;device driver ordering&lt;/li&gt;
&lt;li&gt;shell environment limits&lt;/li&gt;
&lt;li&gt;compatibility shims&lt;/li&gt;
&lt;li&gt;profile selection at boot&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The shape of your software experience depended on this pre-application contract.&lt;/p&gt;
&lt;p&gt;Take a typical line like:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DOS=HIGH,UMB&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This is not a minor tweak. It is a policy statement about reclaiming conventional memory by relocating DOS and enabling upper memory blocks. The decision directly affects whether demanding software starts at all. On constrained systems, architecture is measurable in kilobytes.&lt;/p&gt;
&lt;p&gt;Similarly:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DEVICE=C:\DOS\EMM386.EXE NOEMS&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;NOEMS&lt;/code&gt; option is a strategic compatibility choice. Some programs require EMS, others run better without the overhead. Choosing this setting without understanding workload is equivalent to shipping an environment optimized for one use case while silently degrading another.&lt;/p&gt;
&lt;p&gt;The best DOS operators treated boot configuration like environment design:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;define target workloads&lt;/li&gt;
&lt;li&gt;map resource constraints&lt;/li&gt;
&lt;li&gt;choose defaults&lt;/li&gt;
&lt;li&gt;create profile variants&lt;/li&gt;
&lt;li&gt;validate with repeatable test matrix&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That process should sound familiar to anyone running modern deployment profiles.&lt;/p&gt;
&lt;p&gt;Order mattered too. Driver initialization sequence could change behavior materially. A mouse driver loaded high might free memory for one app. Loaded low, it might block a game from launching. CD extensions, caching layers, and compatibility utilities formed a boot dependency graph, even if no one called it that.&lt;/p&gt;
&lt;p&gt;Dependency graphs existed long before package managers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;FILES=&lt;/code&gt;, &lt;code&gt;BUFFERS=&lt;/code&gt;, and &lt;code&gt;STACKS=&lt;/code&gt; lines are another example of policy in disguise. Too low, and software fails unpredictably. Too high, and scarce memory is wasted. Right-sizing these parameters required understanding workload behavior, not copying internet snippets.&lt;/p&gt;
&lt;p&gt;This is why blindly sharing &amp;ldquo;ultimate CONFIG.SYS&amp;rdquo; templates often failed. Configurations are context-specific.&lt;/p&gt;
&lt;p&gt;Boot menus made this explicit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;profile A for development tools&lt;/li&gt;
&lt;li&gt;profile B for memory-hungry games&lt;/li&gt;
&lt;li&gt;profile C for diagnostics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each profile encoded a different architecture for the same machine. Modern analogy: environment-specific manifests for build, test, and production. Same codebase, different runtime envelopes.&lt;/p&gt;
&lt;p&gt;Reliability also improved when teams documented intent inline. A comment like &amp;ldquo;NOEMS to maximize conventional memory for compiler&amp;rdquo; prevents accidental reversal months later. Without intent, configuration files become superstition archives.&lt;/p&gt;
&lt;p&gt;Superstition-driven config is fragile by definition.&lt;/p&gt;
&lt;p&gt;A practical DOS validation routine looked like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;boot each profile cleanly&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;MEM /C&lt;/code&gt; and record map&lt;/li&gt;
&lt;li&gt;execute representative app set&lt;/li&gt;
&lt;li&gt;observe startup/exit stability&lt;/li&gt;
&lt;li&gt;compare before/after when changing one line&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice the discipline: one change at a time, evidence over intuition.&lt;/p&gt;
&lt;p&gt;Error handling in this layer was unforgiving. Misconfigured drivers could fail silently, partially initialize, or create cascading side effects. Because visibility was limited, operators learned to create minimal recovery profiles with the smallest viable boot path.&lt;/p&gt;
&lt;p&gt;That is classic blast-radius control.&lt;/p&gt;
&lt;p&gt;There is a deeper lesson here: architecture is not only frameworks and diagrams. Architecture is every decision that constrains behavior under load, failure, and variation. &lt;code&gt;CONFIG.SYS&lt;/code&gt; happened to expose those decisions in plain text.&lt;/p&gt;
&lt;p&gt;Modern systems sometimes hide these boundaries behind abstractions. Useful abstractions can improve productivity, but hidden boundaries can degrade operator intuition. DOS taught boundary awareness because it had no room for illusion.&lt;/p&gt;
&lt;p&gt;You felt every tradeoff:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;startup speed versus memory footprint&lt;/li&gt;
&lt;li&gt;compatibility versus performance&lt;/li&gt;
&lt;li&gt;convenience drivers versus deterministic behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those tradeoffs still define system design, only at different scales.&lt;/p&gt;
&lt;p&gt;Another quality of &lt;code&gt;CONFIG.SYS&lt;/code&gt; is deterministic startup. If boot succeeded and expected modules loaded, runtime assumptions were fairly stable. That determinism made troubleshooting tractable. In modern distributed stacks, we often lose this simplicity and then pay for observability infrastructure to recover it.&lt;/p&gt;
&lt;p&gt;The takeaway is not &amp;ldquo;go back to DOS.&amp;rdquo; The takeaway is to preserve explicitness:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;declare startup assumptions&lt;/li&gt;
&lt;li&gt;document resource policies&lt;/li&gt;
&lt;li&gt;version environment configurations&lt;/li&gt;
&lt;li&gt;test profile variants routinely&lt;/li&gt;
&lt;li&gt;maintain a minimal safe-mode path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These practices transfer directly.&lt;/p&gt;
&lt;p&gt;A surprising amount of incident response pain comes from undocumented environment behavior. DOS users could not afford undocumented behavior because failures were immediate and local. We can still adopt that discipline voluntarily.&lt;/p&gt;
&lt;p&gt;If you revisit &lt;code&gt;CONFIG.SYS&lt;/code&gt; today, read it as a tiny architecture document:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what the system prioritizes&lt;/li&gt;
&lt;li&gt;what compatibility it chooses&lt;/li&gt;
&lt;li&gt;how it handles scarcity&lt;/li&gt;
&lt;li&gt;how it recovers from misconfiguration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are architecture questions in any era.&lt;/p&gt;
&lt;p&gt;The file format may look old, but the thinking is modern: explicit policies, constrained resources, and testable configuration states. Good systems engineering has always looked like this.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Interrupts as User Interface</title>
      <link>https://turbovision.in6-addr.net/retro/dos/interrupts-as-user-interface/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:06:14 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/interrupts-as-user-interface/</guid>
      <description>&lt;p&gt;In modern systems, user interface usually means windows, widgets, and event loops. In classic DOS environments, the interface boundary often looked very different: software interrupts. INT calls were not only low-level plumbing; they were stable contracts that programs used as operating surfaces for display, input, disk services, time, and devices.&lt;/p&gt;
&lt;p&gt;Thinking about interrupts as a user interface reveals why DOS programming felt both constrained and elegant. You were not calling giant frameworks. You were speaking a compact protocol: registers in, registers out, carry flag for status, documented side effects.&lt;/p&gt;
&lt;p&gt;Take INT 21h, the core DOS service API. It offered file IO, process management, memory functions, and console interaction. A text tool could feel interactive and polished while relying entirely on these calls and a handful of conventions. The interface was narrow but predictable.&lt;/p&gt;
&lt;p&gt;INT 10h for video and INT 16h for keyboard provided another layer. Combined, they formed a practical interaction stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;render character cells&lt;/li&gt;
&lt;li&gt;move cursor&lt;/li&gt;
&lt;li&gt;read key events&lt;/li&gt;
&lt;li&gt;update state machine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is a full UI model, just encoded in BIOS and DOS vectors instead of GUI widget trees.&lt;/p&gt;
&lt;p&gt;The benefit of such interfaces is explicitness. Every call had a cost and a contract. You learned quickly that &amp;ldquo;just redraw everything&amp;rdquo; may flicker and waste cycles, while selective redraws feel responsive even on modest hardware.&lt;/p&gt;
&lt;p&gt;A classic loop looked like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;read key via INT 16h&lt;/li&gt;
&lt;li&gt;map key to command/state transition&lt;/li&gt;
&lt;li&gt;update model&lt;/li&gt;
&lt;li&gt;repaint affected cells only&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This remains good architecture. Event input, state transition, minimal render diff.&lt;/p&gt;
&lt;p&gt;Interrupt-driven design also encouraged compatibility thinking. Programs often needed to run across BIOS implementations, DOS variants, and quirky hardware clones. Defensive coding around return flags and capability checks became normal practice.&lt;/p&gt;
&lt;p&gt;Modern equivalent? Feature detection, graceful fallback, and compatibility shims.&lt;/p&gt;
&lt;p&gt;Error handling through flags and return codes built good habits too. You did not get exception stacks by default. You checked outcomes explicitly and handled failure paths intentionally. That style can feel verbose, but it produces robust control flow when applied consistently.&lt;/p&gt;
&lt;p&gt;There was, of course, danger. Interrupt vectors could be hooked by TSRs and drivers. Programs sharing this environment had to coexist with unknown residents. Hook chains, reentrancy concerns, and timing assumptions made debugging subtle.&lt;/p&gt;
&lt;p&gt;Yet this ecosystem also taught composability. TSRs could extend behavior without source-level integration. Keyboard enhancers, clipboard utilities, and menu overlays effectively acted like plugins implemented through interrupt interception.&lt;/p&gt;
&lt;p&gt;The modern analogy is middleware and event interception layers. Different mechanism, same concept.&lt;/p&gt;
&lt;p&gt;Performance literacy was unavoidable. Each interrupt call touched real hardware pathways and constrained memory. Programmers learned to batch operations, avoid unnecessary mode switches, and cache where safe. This is still relevant in latency-sensitive systems.&lt;/p&gt;
&lt;p&gt;A practical lesson from INT-era code is interface minimalism. Many successful DOS tools provided excellent usability with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clear hotkeys&lt;/li&gt;
&lt;li&gt;deterministic screen layout&lt;/li&gt;
&lt;li&gt;immediate feedback&lt;/li&gt;
&lt;li&gt;low startup cost&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No animation. No ornamental complexity. Just direct control and predictable behavior.&lt;/p&gt;
&lt;p&gt;Documentation quality mattered more too. Because interfaces were low-level, good comments and reference notes were essential. Teams that documented register usage, assumptions, and tested configurations shipped software that survived beyond one machine setup.&lt;/p&gt;
&lt;p&gt;If you revisit DOS programming today, treat interrupts not as relics but as case studies in API design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;small surface&lt;/li&gt;
&lt;li&gt;explicit contracts&lt;/li&gt;
&lt;li&gt;predictable error signaling&lt;/li&gt;
&lt;li&gt;compatibility-aware behavior&lt;/li&gt;
&lt;li&gt;measurable performance characteristics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are timeless properties of good interfaces.&lt;/p&gt;
&lt;p&gt;There is also a philosophical takeaway: user experience does not require visual complexity. A system can feel excellent when response is immediate, controls are learnable, and failure states are understandable. Interrupt-era tools often got this right under severe constraints.&lt;/p&gt;
&lt;p&gt;You can even apply this mindset to current CLI and TUI projects. Build narrow, well-documented interfaces first. Keep interactions deterministic. Prioritize startup speed and feedback latency. Reserve abstraction for proven pain points, not speculative architecture.&lt;/p&gt;
&lt;p&gt;Interrupts as user interface is not about romanticizing old APIs. It is about recognizing that good interaction design can emerge from strict contracts and constrained channels. The medium may change, but the principles endure.&lt;/p&gt;
&lt;p&gt;When software feels clear, responsive, and dependable, users rarely care whether the plumbing is modern or vintage. They care that the contract holds. DOS interrupts were contracts, and in that sense they were very much a UI language.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>IRQ Maps and the Politics of Slots</title>
      <link>https://turbovision.in6-addr.net/retro/hardware/irq-maps-and-the-politics-of-slots/</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/hardware/irq-maps-and-the-politics-of-slots/</guid>
      <description>&lt;p&gt;Anyone who built or maintained DOS-era PCs remembers that hardware conflicts were not rare edge cases; they were normal engineering terrain. IRQ lines, DMA channels, and I/O addresses had to be negotiated manually, and each new card could destabilize a previously stable system. This was less like plug-and-play and more like coalition politics in a fragile parliament.&lt;/p&gt;
&lt;p&gt;The core constraint was scarcity. Popular sound cards wanted IRQ 5 or 7. Network cards often preferred 10 or 11 on later boards but collided with other devices on mixed systems. Serial ports claimed fixed ranges by convention. Printer ports occupied addresses and IRQs that software still expected. These were not abstract settings. They were finite shared resources, and two devices claiming the same line could produce failures that looked random until you mapped the whole system.&lt;/p&gt;
&lt;p&gt;That mapping step separated casual tinkering from reliable operation. Good builders kept a notebook: slot position, card model, jumper settings, base address, IRQ, DMA low/high, BIOS toggles, and driver load order. Without this, every change became archaeology. With it, you could reason about conflicts before booting and recover quickly after experiments.&lt;/p&gt;
&lt;p&gt;Slot placement itself mattered more than many people remember. Motherboards often wired specific slots to shared interrupt paths or delivered different electrical behavior under load. Moving a card one slot over could stabilize an entire system. This felt superstitious until you understood board traces, chipset quirks, and timing sensitivities. “Try another slot” was not a meme; it was an informed diagnostic move.&lt;/p&gt;
&lt;p&gt;Software configuration had to align with hardware reality. A sound card set to IRQ 5 physically but configured as IRQ 7 in a game setup utility produced symptoms that were confusing but consistent: missing effects, lockups during sample playback, or intermittent crackle. The fix was not mystical. It was alignment across all layers: jumper, driver, environment variable, and application profile.&lt;/p&gt;
&lt;p&gt;Boot profiles in &lt;code&gt;CONFIG.SYS&lt;/code&gt; and &lt;code&gt;AUTOEXEC.BAT&lt;/code&gt; were a practical strategy for managing these tensions. One profile could prioritize networking and tooling, another multimedia and joystick support, another minimal diagnostics with most TSRs disabled. This profile pattern is a direct ancestor of modern environment presets. The principle is the same: explicit runtime compositions for different goals.&lt;/p&gt;
&lt;p&gt;DMA conflicts introduced their own flavor of pain. Two devices fighting over transfer channels could produce corruption that looked like software bugs. Audio glitches, disk anomalies, and sporadic crashes were common misdiagnoses. Experienced builders verified resource assignment first, then software assumptions. This order saved hours and prevented unnecessary reinstalls.&lt;/p&gt;
&lt;p&gt;Another historical lesson is that documentation quality varied wildly. Some clone cards shipped with sparse manuals or contradictory defaults. Community knowledge filled gaps: magazine columns, BBS archives, user groups, and handwritten cheatsheets. Effective troubleshooting required combining official docs with field reports. This mirrors contemporary reality where vendor documentation and community issue threads jointly form operational truth.&lt;/p&gt;
&lt;p&gt;The social side mattered too. In many places, one local expert became the de facto “slot diplomat,” helping classmates, coworkers, or club members resolve impossible-seeming conflicts. These people were not wizards. They were disciplined observers with good records and patience. Their method was repeatable: isolate, simplify, reassign, retest, document.&lt;/p&gt;
&lt;p&gt;From a design perspective, this era teaches respect for explicit resource models. Automatic negotiation is convenient, and modern systems rightly hide many details. But when abstraction fails, teams still need people who can reason from first principles. IRQ maps are old, yet the mindset transfers directly to container port collisions, PCI passthrough issues, interrupt storms, and shared resource exhaustion in current stacks.&lt;/p&gt;
&lt;p&gt;If you ever rebuild a vintage machine, treat slot planning as architecture, not housekeeping. Define requirements first: audio reliability, network throughput, serial compatibility, low-noise operation, diagnostic observability. Then assign resources intentionally, keep a change log, and resist random edits under fatigue. Stability is usually the outcome of boring discipline, not lucky jumper positions.&lt;/p&gt;
&lt;p&gt;The romance of retro hardware often focuses on aesthetics: beige cases, mechanical switches, CRT glow. The deeper craft was operational negotiation under constraint. IRQ maps were part of that craft. They made you model the whole system, validate assumptions layer by layer, and write down what you learned so the next failure started from knowledge, not myth.&lt;/p&gt;
&lt;p&gt;That documentation habit is probably the most transferable lesson. Whether you are assigning IRQs on ISA cards or allocating shared resources in modern infrastructure, stable systems are usually the result of explicit maps, deliberate ownership, and controlled change. The names changed. The engineering pattern did not.&lt;/p&gt;
&lt;h2 id=&#34;practical-irq-map-example&#34;&gt;Practical IRQ map example&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;SB16 clone      A220 I5 D1 H5
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;NE2000 ISA      IRQ10 IO300
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;COM1/COM2       IRQ4 / IRQ3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;LPT1            IRQ7 (disabled if audio needs IRQ7)&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;The exact values vary by board and card set, but writing this table down before changes prevents blind conflict loops.&lt;/p&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/hardware/restoring-a-286/&#34;&gt;Restoring an AT 286&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/config-sys-as-architecture/&#34;&gt;CONFIG.SYS as Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/interrupts-as-user-interface/&#34;&gt;Interrupts as User Interface&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Mode 13h in Turbo Pascal: Graphics Programming Without Illusions</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/mode-13h-graphics-in-turbo-pascal/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:23:45 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/mode-13h-graphics-in-turbo-pascal/</guid>
      <description>&lt;p&gt;Turbo Pascal graphics programming is one of the cleanest ways to learn what a frame actually is. In modern stacks, rendering often passes through layers that hide timing, memory layout, and write costs. In DOS Mode 13h, almost nothing is hidden. You get 320x200, 256 colors, and a linear framebuffer at segment &lt;code&gt;$A000&lt;/code&gt;. Every pixel you draw is your responsibility.&lt;/p&gt;
&lt;p&gt;Mode 13h became a favorite because it removed complexity that earlier VGA modes imposed. No planar bit operations, no complicated bank switching for this resolution, and no mystery about where bytes go. Pixel &lt;code&gt;(x, y)&lt;/code&gt; maps to offset &lt;code&gt;y * 320 + x&lt;/code&gt;. That directness made it ideal for demos, games, and educational experiments. It rewarded people who could reason about memory as geometry.&lt;/p&gt;
&lt;p&gt;A minimal setup in Turbo Pascal is refreshingly explicit: switch video mode via BIOS interrupt, get access to VGA memory, write bytes, wait for input, restore text mode. There is no rendering engine to configure. You control lifecycle directly. That means you also own failure states. Forget to restore mode and you leave the user in graphics. Corrupt memory and artifacts appear instantly.&lt;/p&gt;
&lt;p&gt;Early experiments usually start with single-pixel writes and quickly hit performance limits. Calling a procedure per pixel is expressive but expensive. The first optimization lesson is batching and locality: draw contiguous spans, avoid repeated multiplies, precompute line offsets, and minimize branch-heavy inner loops. Mode 13h teaches a truth that still holds in GPU-heavy times: throughput loves predictable memory access.&lt;/p&gt;
&lt;p&gt;Palette control is another powerful concept students often miss today. In 256-color mode, pixel values are indices, not direct RGB triples. By writing DAC registers, you can change global color mappings without touching framebuffer bytes. This enables palette cycling, day-night transitions, and cheap animation effects that look far richer than their computational cost. You are effectively animating interpretation, not data.&lt;/p&gt;
&lt;p&gt;The classic water or fire effects in DOS demos relied on exactly this trick. The framebuffer stayed mostly stable while the palette rotated across carefully constructed ramps. What looked dynamic and expensive was often elegant indirection. When people say old graphics programmers were “clever,” this is the kind of system-level cleverness they mean: using hardware semantics to trade bandwidth for perception.&lt;/p&gt;
&lt;p&gt;Flicker management introduces the next lesson: page or buffer discipline. If you draw directly to visible memory while the beam is scanning, partial updates can tear. So many projects used software backbuffers in conventional memory, composed full frames there, then copied to &lt;code&gt;$A000&lt;/code&gt; in one pass. With tight loops and occasional retrace synchronization, output became dramatically cleaner. This is conceptually the same as modern double buffering.&lt;/p&gt;
&lt;p&gt;Collision and sprite systems further sharpen design. Transparent blits require skipping designated color indices. Masking introduces branch costs. Dirty-rectangle approaches reduce full-screen copies at the price of bookkeeping complexity. Developers learned to choose trade-offs based on scene characteristics instead of blindly applying one pattern. That mindset remains essential in performance engineering: no optimization is universal.&lt;/p&gt;
&lt;p&gt;Turbo Pascal itself played a practical role in this loop. You could prototype an effect in high-level Pascal, profile by observation, then move only hotspot routines to inline assembly where needed. That incremental path is important. It discouraged premature optimization while still allowing low-level control when measurable bottlenecks appeared. Good systems work often looks like this staircase: clarity first, precision optimization second.&lt;/p&gt;
&lt;p&gt;Debugging graphics bugs in Mode 13h was brutally educational. Off-by-one writes painted diagonal scars. Incorrect stride assumptions created skewed images. Overflow in offset arithmetic wrapped into nonsense that looked artistic until it crashed. You learned to verify bounds, separate coordinate transforms from blitting, and build tiny visual test patterns. A checkerboard routine can reveal more than pages of logging.&lt;/p&gt;
&lt;p&gt;One underused exercise for modern learners is implementing the same tiny scene three ways: naive per-pixel draw, scanline-optimized draw, and buffered blit with palette animation. The visual output can be identical while performance differs radically. This makes optimization tangible. You are not guessing from profiler flames alone; you see smoothness and latency with your own eyes.&lt;/p&gt;
&lt;p&gt;Mode 13h also teaches humility about hardware assumptions. Not every machine behaves the same under load. Timing differences, cache behavior, and peripheral quirks affect results. The cleanest DOS codebases separated device assumptions from scene logic and made fallbacks possible. That sounds like old wisdom, but it maps directly to current cross-platform rendering work.&lt;/p&gt;
&lt;p&gt;There is a reason this environment remains compelling decades later. It compresses core graphics principles into a small, understandable box: memory addressing, color representation, buffering strategy, and frame pacing. You can hold the whole pipeline in your head. Once you can do that, modern APIs feel less magical and more like powerful abstractions built on familiar physics.&lt;/p&gt;
&lt;p&gt;Turbo Pascal in Mode 13h is therefore not a relic exercise. It is a precision training ground. It teaches you to respect data movement, to decouple representation from display, to optimize where evidence points, and to treat visual correctness as testable behavior. Those lessons survive every framework trend because they are not about tools. They are about first principles.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mode X in Turbo Pascal, Part 1: Planar Memory and Pages</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-1-planar-memory-model/</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/modex/modex-part-1-planar-memory-model/</guid>
      <description>&lt;p&gt;Mode 13h is the famous VGA &amp;ldquo;easy mode&amp;rdquo;: one byte per pixel, 320x200, 256 colors, linear memory. It is perfect for first experiments and still great for teaching rendering basics. But old DOS games that felt smoother than your own early experiments usually did not stop there. They switched to Mode X style layouts where planar memory, off-screen pages, and explicit register control gave better composition options and cleaner timing.&lt;/p&gt;
&lt;p&gt;This first article in the series is about that mental model. Before writing sprite engines, tile systems, or palette tricks, you need to understand what the VGA memory controller is really doing. If the model is wrong, every optimization turns into folklore.&lt;/p&gt;
&lt;p&gt;If you have not read &lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/mode-13h-graphics-in-turbo-pascal/&#34;&gt;Mode 13h Graphics in Turbo Pascal&lt;/a&gt;, do that first. It gives the baseline we are now deliberately leaving behind.&lt;/p&gt;
&lt;h2 id=&#34;why-mode-x-felt-faster-in-real-games&#34;&gt;Why Mode X felt &amp;ldquo;faster&amp;rdquo; in real games&lt;/h2&gt;
&lt;p&gt;The practical advantage was not raw arithmetic speed. The advantage was control over layout and buffering:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You could keep multiple pages in video memory.&lt;/li&gt;
&lt;li&gt;You could build into a hidden page and flip start address.&lt;/li&gt;
&lt;li&gt;You could organize writes in ways that matched planar hardware better.&lt;/li&gt;
&lt;li&gt;You could avoid tearing without full-frame copies every frame.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What looked like magic in magazines was mostly disciplined memory mapping plus stable frame pacing.&lt;/p&gt;
&lt;h2 id=&#34;the-key-shift-from-linear-bytes-to-planes&#34;&gt;The key shift: from linear bytes to planes&lt;/h2&gt;
&lt;p&gt;In Mode X style operation, pixel bytes are distributed across four planes. Adjacent pixel columns are not consecutive memory bytes in the way Mode 13h beginners expect. Instead, pixel ownership rotates by plane. That means one memory offset can represent four neighboring pixels depending on which plane is currently enabled for writes.&lt;/p&gt;
&lt;p&gt;The control knobs are VGA registers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sequencer map mask: choose writable plane(s).&lt;/li&gt;
&lt;li&gt;Graphics controller read map select: choose readable plane.&lt;/li&gt;
&lt;li&gt;CRTC start address: choose which memory area is currently displayed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you accept that &amp;ldquo;address + selected plane = pixel target,&amp;rdquo; most confusing behavior suddenly becomes deterministic.&lt;/p&gt;
&lt;h2 id=&#34;entering-a-workable-320x240-like-unchained-setup&#34;&gt;Entering a workable 320x240-like unchained setup&lt;/h2&gt;
&lt;p&gt;Many implementations start by setting BIOS mode 13h and then unchaining to get planar behavior while keeping convenient geometry assumptions. Exact register recipes vary by card and emulator, so treat this as a pattern, not sacred scripture.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure SetModeX;
begin
  asm
    mov ax, $0013
    int $10
  end;

  { Disable chain-4 and odd/even, enable all planes }
  Port[$3C4] := $04; Port[$3C5] := $06; { Memory Mode }
  Port[$3C4] := $02; Port[$3C5] := $0F; { Map Mask }

  { Graphics controller tweaks for unchained access }
  Port[$3CE] := $05; Port[$3CF] := $40;
  Port[$3CE] := $06; Port[$3CF] := $05;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Do not panic if this looks low-level. Turbo Pascal is excellent at this style of direct hardware work because compile-run cycles are fast and failures are usually immediately observable.&lt;/p&gt;
&lt;h2 id=&#34;plotting-one-pixel-with-plane-selection&#34;&gt;Plotting one pixel with plane selection&lt;/h2&gt;
&lt;p&gt;A minimal pixel routine makes the model tangible. X chooses plane and byte offset; Y chooses row stride component.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure PutPixelX(X, Y: Integer; C: Byte);
var
  Offset: Word;
  PlaneMask: Byte;
begin
  Offset := (Y * 80) + (X shr 2);
  PlaneMask := 1 shl (X and 3);

  Port[$3C4] := $02;
  Port[$3C5] := PlaneMask;
  Mem[$A000:Offset] := C;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;80&lt;/code&gt; stride comes from 320/4 bytes per row in planar addressing. That single number is where many beginner bugs hide, because linear assumptions die hard.&lt;/p&gt;
&lt;h2 id=&#34;pages-and-start-address-flipping&#34;&gt;Pages and start address flipping&lt;/h2&gt;
&lt;p&gt;A stronger reason to adopt Mode X is page strategy. If your card memory budget allows it, maintain two or more page regions in VRAM. Render into non-visible page, then point CRTC start address at the finished page. That is cheaper and cleaner than copying full frames through CPU-visible loops every tick.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;displayPage&lt;/code&gt; is what CRTC shows.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drawPage&lt;/code&gt; is where your renderer writes.&lt;/li&gt;
&lt;li&gt;End of frame: swap roles and update CRTC start.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code details differ by implementation, but the discipline is universal: never draw directly into the page currently being scanned out unless you enjoy tear artifacts as design motif.&lt;/p&gt;
&lt;h2 id=&#34;practical-debugging-advice&#34;&gt;Practical debugging advice&lt;/h2&gt;
&lt;p&gt;When output is wrong, do not &amp;ldquo;optimize harder.&amp;rdquo; Validate one axis at a time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fill one plane with a color and confirm stripe pattern.&lt;/li&gt;
&lt;li&gt;Write known values at fixed offsets and read back by plane.&lt;/li&gt;
&lt;li&gt;Verify start-address page flip without any sprite code.&lt;/li&gt;
&lt;li&gt;Only then add primitives and scene logic.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This sequence saves hours. Most graphics bugs in this phase are addressing bugs, not &amp;ldquo;algorithm bugs.&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;where-we-go-next&#34;&gt;Where we go next&lt;/h2&gt;
&lt;p&gt;In Part 2, we build practical drawing primitives (lines, rectangles, clipped blits) that respect planar layout instead of fighting it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-2-primitives-and-clipping/&#34;&gt;Mode X in Turbo Pascal, Part 2: Primitives and Clipping&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Related context:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-in-2025/&#34;&gt;Writing Turbo Pascal in 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/batch-file-wizardry/&#34;&gt;Batch File Wizardry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/config-sys-as-architecture/&#34;&gt;CONFIG.SYS as Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mode X is not difficult because it is old. It is difficult because it requires a precise mental model. Once that model clicks, the hardware starts to feel less like a trap and more like an instrument.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mode X in Turbo Pascal, Part 2: Primitives and Clipping</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-2-primitives-and-clipping/</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/modex/modex-part-2-primitives-and-clipping/</guid>
      <description>&lt;p&gt;After the planar memory model clicks, the next trap is pretending linear drawing code can be &amp;ldquo;ported&amp;rdquo; to Mode X by changing one helper. That works for demos and fails for games. Robust Mode X rendering starts with primitives that are aware of planes, clipping, and page targets from day one.&lt;/p&gt;
&lt;p&gt;If you missed the foundation, begin with &lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-1-planar-memory-model/&#34;&gt;Part 1: Planar Memory and Pages&lt;/a&gt;. This article assumes you already have working pixel output and page flipping.&lt;/p&gt;
&lt;h2 id=&#34;primitive-design-goals&#34;&gt;Primitive design goals&lt;/h2&gt;
&lt;p&gt;For old DOS rendering pipelines, primitives should optimize for correctness first:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Never write outside page bounds.&lt;/li&gt;
&lt;li&gt;Keep clipping deterministic and centralized.&lt;/li&gt;
&lt;li&gt;Minimize per-pixel register churn where possible.&lt;/li&gt;
&lt;li&gt;Separate addressing math from shape logic.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Performance matters, but undefined writes kill performance faster than any missing micro-optimization.&lt;/p&gt;
&lt;h2 id=&#34;clipping-is-policy-not-an-afterthought&#34;&gt;Clipping is policy, not an afterthought&lt;/h2&gt;
&lt;p&gt;A common beginner pattern is &amp;ldquo;draw first, check later.&amp;rdquo; On VGA memory that quickly becomes silent corruption. Instead, apply clipping at primitive boundaries before entering the hot loops.&lt;/p&gt;
&lt;p&gt;For axis-aligned boxes, clipping is straightforward:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;function ClipRect(var X1, Y1, X2, Y2: Integer): Boolean;
begin
  if X1 &amp;lt; 0 then X1 := 0;
  if Y1 &amp;lt; 0 then Y1 := 0;
  if X2 &amp;gt; 319 then X2 := 319;
  if Y2 &amp;gt; 199 then Y2 := 199;
  ClipRect := (X1 &amp;lt;= X2) and (Y1 &amp;lt;= Y2);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once clipped, your inner loop can stay simple and trustworthy. This is less glamorous than fancy blitters and infinitely more important.&lt;/p&gt;
&lt;h2 id=&#34;horizontal-fills-with-reduced-state-changes&#34;&gt;Horizontal fills with reduced state changes&lt;/h2&gt;
&lt;p&gt;Naive pixel-by-pixel fills set map mask every write. Better approach: process spans in groups where plane mask pattern repeats predictably. Even a modest rework reduces I/O pressure.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure HLineX(X1, X2, Y: Integer; C: Byte);
var
  X: Integer;
begin
  if (Y &amp;lt; 0) or (Y &amp;gt; 199) then Exit;
  if X1 &amp;gt; X2 then begin X := X1; X1 := X2; X2 := X; end;
  if X1 &amp;lt; 0 then X1 := 0;
  if X2 &amp;gt; 319 then X2 := 319;

  for X := X1 to X2 do
    PutPixelX(X, Y, C);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This still calls &lt;code&gt;PutPixelX&lt;/code&gt;, but with clipping discipline built in. Later you can specialize spans and batch by plane.&lt;/p&gt;
&lt;h2 id=&#34;rectangle-fills-and-ui-panels&#34;&gt;Rectangle fills and UI panels&lt;/h2&gt;
&lt;p&gt;Old DOS interfaces often combine world rendering plus overlays. A clipped rectangle fill is the workhorse for panels, bars, and damage flashes.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure FillRectX(X1, Y1, X2, Y2: Integer; C: Byte);
var
  Y: Integer;
begin
  if not ClipRect(X1, Y1, X2, Y2) then Exit;
  for Y := Y1 to Y2 do
    HLineX(X1, X2, Y, C);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It looks boring because good infrastructure often does. Boring primitives are stable primitives.&lt;/p&gt;
&lt;h2 id=&#34;line-drawing-without-hidden-chaos&#34;&gt;Line drawing without hidden chaos&lt;/h2&gt;
&lt;p&gt;For general lines, Bresenham remains practical. The Mode X-specific advice is to keep the stepping algorithm independent from memory layout and delegate write target handling to one consistent pixel primitive.&lt;/p&gt;
&lt;p&gt;Why this matters: when bugs appear, you can isolate whether the issue is geometric stepping or planar addressing. Mixed concerns create mixed failures and bad debugging sessions.&lt;/p&gt;
&lt;h2 id=&#34;instrument-your-renderer-early&#34;&gt;Instrument your renderer early&lt;/h2&gt;
&lt;p&gt;Before moving to sprites, add a diagnostic frame:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;draw clipped and unclipped test rectangles at edges&lt;/li&gt;
&lt;li&gt;draw diagonal lines through all corners&lt;/li&gt;
&lt;li&gt;render page index and frame counter&lt;/li&gt;
&lt;li&gt;flash a corner pixel each frame&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If this test scene is unstable, your game scene will be chaos with better art.&lt;/p&gt;
&lt;h2 id=&#34;structured-pass-order&#34;&gt;Structured pass order&lt;/h2&gt;
&lt;p&gt;A practical frame pipeline in Mode X might be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;clear draw page&lt;/li&gt;
&lt;li&gt;draw background spans&lt;/li&gt;
&lt;li&gt;draw world primitives&lt;/li&gt;
&lt;li&gt;draw sprite layer placeholders&lt;/li&gt;
&lt;li&gt;draw HUD rectangles/text&lt;/li&gt;
&lt;li&gt;flip display page&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This ordering gives deterministic overdraw and clear extension points for Part 3.&lt;/p&gt;
&lt;h2 id=&#34;cross-reference-with-existing-dos-workflow&#34;&gt;Cross-reference with existing DOS workflow&lt;/h2&gt;
&lt;p&gt;These graphics routines live inside the same operational reality as your boot and tooling discipline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/interrupts-as-user-interface/&#34;&gt;Interrupts as User Interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/config-sys-as-architecture/&#34;&gt;CONFIG.SYS as Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-before-the-web/&#34;&gt;Turbo Pascal Before the Web&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Old graphics programming is rarely &amp;ldquo;graphics only.&amp;rdquo; It is always an ecosystem of memory policy, startup profile, and debugging rhythm.&lt;/p&gt;
&lt;h2 id=&#34;next-step&#34;&gt;Next step&lt;/h2&gt;
&lt;p&gt;Part 3 moves from primitives to actual game-feeling output: masked sprites, palette cycling, and timing control:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-3-sprites-and-palette-cycling/&#34;&gt;Mode X in Turbo Pascal, Part 3: Sprites and Palette Cycling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Primitives are where reliability is born. If your clips are correct and your spans are deterministic, everything built above them gets cheaper to reason about.&lt;/p&gt;
&lt;p&gt;One extra practice that helps immediately is recording a tiny &amp;ldquo;primitive conformance&amp;rdquo; script in your repo: expected screenshots or checksum-like pixel probes for a fixed test scene. Run it after every renderer change. In retro projects, visual regressions often creep in from seemingly unrelated optimizations, and this one habit catches them early.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mode X in Turbo Pascal, Part 3: Sprites and Palette Cycling</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-3-sprites-and-palette-cycling/</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/modex/modex-part-3-sprites-and-palette-cycling/</guid>
      <description>&lt;p&gt;Sprites are where a renderer starts to feel like a game engine. In Mode X, the challenge is not just drawing images quickly. The challenge is managing transparency, overlap order, and visual dynamism while staying within the strict memory and bandwidth constraints of VGA-era hardware.&lt;/p&gt;
&lt;p&gt;If your primitives and clipping are not stable yet, go back to &lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-2-primitives-and-clipping/&#34;&gt;Part 2&lt;/a&gt;. Sprite bugs are hard enough without foundational uncertainty.&lt;/p&gt;
&lt;h2 id=&#34;sprite-data-strategy-keep-it-explicit&#34;&gt;Sprite data strategy: keep it explicit&lt;/h2&gt;
&lt;p&gt;A reliable sprite pipeline separates three concerns:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Source pixel data.&lt;/li&gt;
&lt;li&gt;Optional transparency mask.&lt;/li&gt;
&lt;li&gt;Draw routine that respects clipping and planes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Trying to &amp;ldquo;infer&amp;rdquo; transparency from arbitrary colors in ad-hoc code works until assets evolve. Use explicit conventions and document them in your asset converter notes.&lt;/p&gt;
&lt;h2 id=&#34;masked-blit-pattern&#34;&gt;Masked blit pattern&lt;/h2&gt;
&lt;p&gt;A classic masked blit uses one pass to preserve destination where mask says transparent, then overlays sprite pixels where opaque. In Turbo Pascal, even simple byte-level logic remains effective if your loops are predictable.&lt;/p&gt;
&lt;p&gt;Pseudo-shape:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;for sy := 0 to SpriteH - 1 do
  for sx := 0 to SpriteW - 1 do
    if Mask[sx, sy] &amp;lt;&amp;gt; 0 then
      PutPixelX(DstX + sx, DstY + sy, Sprite[sx, sy]);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can optimize later with span-based opaque runs. First make it correct under clipping and page boundaries.&lt;/p&gt;
&lt;h2 id=&#34;clipping-sprites-without-branching-chaos&#34;&gt;Clipping sprites without branching chaos&lt;/h2&gt;
&lt;p&gt;A practical trick: precompute clipped source and destination windows once per sprite draw call. Then inner loops run branch-light:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;srcStartX/srcStartY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;srcEndX/srcEndY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dstStartX/dstStartY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This keeps the &amp;ldquo;should I draw this pixel?&amp;rdquo; decision out of every iteration and dramatically reduces bug surface.&lt;/p&gt;
&lt;h2 id=&#34;draw-order-as-policy&#34;&gt;Draw order as policy&lt;/h2&gt;
&lt;p&gt;In old-school 2D engines, z-order usually means &amp;ldquo;draw in sorted sequence.&amp;rdquo; Keep that sequence explicit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;background&lt;/li&gt;
&lt;li&gt;terrain decals&lt;/li&gt;
&lt;li&gt;actors&lt;/li&gt;
&lt;li&gt;projectiles&lt;/li&gt;
&lt;li&gt;effects&lt;/li&gt;
&lt;li&gt;HUD&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When overlap glitches appear, deterministic order lets you debug with confidence instead of guessing whether timing or memory corruption is involved.&lt;/p&gt;
&lt;h2 id=&#34;palette-cycling-cheap-motion-strong-mood&#34;&gt;Palette cycling: cheap motion, strong mood&lt;/h2&gt;
&lt;p&gt;Palette tricks are one of the most useful VGA-era superpowers. Instead of rewriting pixel memory, rotate a subset of palette entries and let existing pixels &amp;ldquo;animate&amp;rdquo; automatically. Water shimmer, terminal glow, warning lights, and magic effects become nearly free per frame.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure RotatePaletteRange(FirstIdx, LastIdx: Byte);
var
  TmpR, TmpG, TmpB: Byte;
  I: Integer;
begin
  { Assume Palette[] holds RGB triples in 0..63 VGA range }
  TmpR := Palette[LastIdx].R;
  TmpG := Palette[LastIdx].G;
  TmpB := Palette[LastIdx].B;
  for I := LastIdx downto FirstIdx + 1 do
    Palette[I] := Palette[I - 1];
  Palette[FirstIdx].R := TmpR;
  Palette[FirstIdx].G := TmpG;
  Palette[FirstIdx].B := TmpB;
  ApplyPaletteRange(FirstIdx, LastIdx);
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The artistic rule is simple: reserve palette bands intentionally. If artists and programmers share the same palette map vocabulary, effects stay predictable.&lt;/p&gt;
&lt;h2 id=&#34;timing-lock-behavior-before-optimization&#34;&gt;Timing: lock behavior before optimization&lt;/h2&gt;
&lt;p&gt;Animation quality depends more on frame pacing than raw speed. Old DOS projects often tied simulation to variable frame rate and then fought phantom bugs for weeks. Better pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;fixed simulation tick (e.g., 70 Hz or 60 Hz equivalent)&lt;/li&gt;
&lt;li&gt;render as often as practical&lt;/li&gt;
&lt;li&gt;interpolate only when necessary&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Even on retro hardware, disciplined timing produces smoother perceived motion than occasional fast spikes.&lt;/p&gt;
&lt;h2 id=&#34;debug-overlays-save-projects&#34;&gt;Debug overlays save projects&lt;/h2&gt;
&lt;p&gt;Add optional overlays you can toggle with a key:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sprite bounding boxes&lt;/li&gt;
&lt;li&gt;clip rectangles&lt;/li&gt;
&lt;li&gt;page index&lt;/li&gt;
&lt;li&gt;tick/frame counters&lt;/li&gt;
&lt;li&gt;palette band IDs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These overlays are not &amp;ldquo;debug clutter.&amp;rdquo; They are observability for graphics systems that otherwise fail visually without explanation.&lt;/p&gt;
&lt;h2 id=&#34;cross-references-that-help-this-stage&#34;&gt;Cross references that help this stage&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/mode-13h-graphics-in-turbo-pascal/&#34;&gt;Mode 13h Graphics in Turbo Pascal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-1-planar-memory-model/&#34;&gt;Mode X Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-2-primitives-and-clipping/&#34;&gt;Mode X Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-in-2025/&#34;&gt;Turbo Pascal in 2025&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each one contributes a different layer: memory model, primitive discipline, and workflow habits.&lt;/p&gt;
&lt;h2 id=&#34;next-article&#34;&gt;Next article&lt;/h2&gt;
&lt;p&gt;Part 4 moves to tilemaps, camera movement, and data streaming from disk into playable scenes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-4-tilemaps-and-streaming/&#34;&gt;Mode X in Turbo Pascal, Part 4: Tilemaps and Streaming&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sprites make a renderer feel alive. Palette cycling makes it feel alive on a budget. Together they are a practical lesson in constraint-driven expressiveness.&lt;/p&gt;
&lt;p&gt;If you maintain this code over time, keep a small palette allocation map next to your asset pipeline notes. Which index bands are reserved for UI, which are cycle-safe, which are gameplay-critical. Teams that write this down once avoid months of accidental palette collisions later.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mode X in Turbo Pascal, Part 4: Tilemaps and Streaming</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-4-tilemaps-and-streaming/</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/modex/modex-part-4-tilemaps-and-streaming/</guid>
      <description>&lt;p&gt;A renderer becomes a game when it can show world-scale structure, not just local effects. That means tilemaps, camera movement, and disciplined data loading. In Mode X-era development, these systems were not optional polish. They were the only way to present rich scenes inside strict memory budgets.&lt;/p&gt;
&lt;p&gt;This final Mode X article focuses on operational structure: how to build scenes that scroll smoothly, load predictably, and remain debuggable.&lt;/p&gt;
&lt;h2 id=&#34;start-with-memory-budget-not-features&#34;&gt;Start with memory budget, not features&lt;/h2&gt;
&lt;p&gt;Before defining map format, set your memory envelope:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;available conventional/extended memory&lt;/li&gt;
&lt;li&gt;VRAM page layout&lt;/li&gt;
&lt;li&gt;sprite and tile cache size&lt;/li&gt;
&lt;li&gt;IO buffer size&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then derive map chunk dimensions from those limits. Teams that reverse the order usually rewrite their map loader halfway through the project.&lt;/p&gt;
&lt;h2 id=&#34;tilemap-schema-that-survives-growth&#34;&gt;Tilemap schema that survives growth&lt;/h2&gt;
&lt;p&gt;A practical map record often includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tile index grid (primary layer)&lt;/li&gt;
&lt;li&gt;collision flags&lt;/li&gt;
&lt;li&gt;optional overlay/effect layer&lt;/li&gt;
&lt;li&gt;spawn metadata&lt;/li&gt;
&lt;li&gt;trigger markers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep versioning in the file header. Old DOS projects often outlived their first map format and paid dearly for &amp;ldquo;quick binary dumps&amp;rdquo; with no compatibility markers.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;type
  TMapHeader = record
    Magic: array[0..3] of Char;  { &amp;#39;MAPX&amp;#39; }
    Version: Word;
    Width, Height: Word;         { in tiles }
    TileW, TileH: Byte;
    LayerCount: Byte;
  end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Version fields are boring until you need to load yesterday&amp;rsquo;s assets under today&amp;rsquo;s executable.&lt;/p&gt;
&lt;h2 id=&#34;camera-math-and-draw-windows&#34;&gt;Camera math and draw windows&lt;/h2&gt;
&lt;p&gt;For each frame:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;determine camera pixel position&lt;/li&gt;
&lt;li&gt;convert to tile-space window&lt;/li&gt;
&lt;li&gt;draw only visible tile rectangle plus one-tile margin&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The one-tile margin prevents edge pop during sub-tile movement. Combine this with clipped blits from &lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-2-primitives-and-clipping/&#34;&gt;Part 2&lt;/a&gt; and you get stable scrolling without full-map redraw.&lt;/p&gt;
&lt;h2 id=&#34;chunked-streaming-from-disk&#34;&gt;Chunked streaming from disk&lt;/h2&gt;
&lt;p&gt;Large maps should be chunked. Load around camera, evict far chunks, keep hot set warm.&lt;/p&gt;
&lt;p&gt;A simple policy works well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;chunk size fixed (for example 32x32 tiles)&lt;/li&gt;
&lt;li&gt;maintain 3x3 chunk neighborhood around camera chunk&lt;/li&gt;
&lt;li&gt;prefetch movement direction neighbor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is not overengineering. On slow storage, missing prefetch translates directly into visible hitching.&lt;/p&gt;
&lt;h2 id=&#34;keep-io-deterministic&#34;&gt;Keep IO deterministic&lt;/h2&gt;
&lt;p&gt;Disk access must avoid unpredictable burst behavior during input-critical moments. Two rules help:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;schedule loads at known frame points (post-render or pre-update)&lt;/li&gt;
&lt;li&gt;cap max bytes read per frame under stress&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When a chunk is not ready, prefer visual fallback tile over frame stall. Small visual degradation is often less disruptive than control latency spikes.&lt;/p&gt;
&lt;h2 id=&#34;practical-cache-keys&#34;&gt;Practical cache keys&lt;/h2&gt;
&lt;p&gt;Use integer chunk coordinates as cache keys. String keys are unnecessary overhead in this environment and complicate diagnostics.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;type
  TChunkKey = record
    CX, CY: SmallInt;
  end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pair keys with explicit state flags: &lt;code&gt;Absent&lt;/code&gt;, &lt;code&gt;Loading&lt;/code&gt;, &lt;code&gt;Ready&lt;/code&gt;, &lt;code&gt;Dirty&lt;/code&gt;. State clarity is more important than clever container choice.&lt;/p&gt;
&lt;h2 id=&#34;hud-and-world-composition&#34;&gt;HUD and world composition&lt;/h2&gt;
&lt;p&gt;Render world layers first, then entities, then HUD into same draw page. Keep HUD draw routines independent from camera transforms. Many old engines leaked camera offsets into UI code and carried that bug tax for years.&lt;/p&gt;
&lt;p&gt;You can validate this quickly by forcing camera to extreme coordinates and checking whether UI still anchors correctly.&lt;/p&gt;
&lt;h2 id=&#34;failure-modes-to-test-intentionally&#34;&gt;Failure modes to test intentionally&lt;/h2&gt;
&lt;p&gt;Test these early, not at content freeze:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;camera crossing chunk boundaries repeatedly&lt;/li&gt;
&lt;li&gt;high-speed movement through dense trigger zones&lt;/li&gt;
&lt;li&gt;partial chunk read failure&lt;/li&gt;
&lt;li&gt;map version mismatch&lt;/li&gt;
&lt;li&gt;missing tile index fallback path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each one should degrade gracefully with explicit logging. Silent corruption is far worse than a visible placeholder tile.&lt;/p&gt;
&lt;h2 id=&#34;cross-references-for-full-pipeline-context&#34;&gt;Cross references for full pipeline context&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-1-planar-memory-model/&#34;&gt;Part 1: Planar Memory and Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-3-sprites-and-palette-cycling/&#34;&gt;Part 3: Sprites and Palette Cycling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-before-the-web/&#34;&gt;Turbo Pascal Before the Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/config-sys-as-architecture/&#34;&gt;CONFIG.SYS as Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These pieces together describe not just rendering, but operation: startup profile, page policy, draw order, and asset logistics.&lt;/p&gt;
&lt;h2 id=&#34;closing-note-on-mode-x-projects&#34;&gt;Closing note on Mode X projects&lt;/h2&gt;
&lt;p&gt;Mode X is often presented as nostalgic low-level craft. It is also a great systems-design classroom. You learn cache boundaries, streaming policies, deterministic updates, and diagnostic overlays in an environment where consequences are immediate.&lt;/p&gt;
&lt;p&gt;If this series worked, you now have a path from first pixel to world-scale scene architecture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;memory model&lt;/li&gt;
&lt;li&gt;primitives&lt;/li&gt;
&lt;li&gt;sprites and timing&lt;/li&gt;
&lt;li&gt;streaming and camera&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That sequence is still useful on modern engines. The APIs changed. The discipline did not.&lt;/p&gt;
&lt;p&gt;Treat your map format docs as part of runtime code quality. A map pipeline without explicit contracts eventually becomes an incident response problem.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal Before the Web: The IDE That Trained a Generation</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-before-the-web/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:23:12 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-before-the-web/</guid>
      <description>&lt;p&gt;Turbo Pascal was more than a compiler. In practice it was a compact school for software engineering, hidden inside a blue screen and distributed on disks you could hold in one hand. Long before tutorials were streamed and before package managers automated everything, Turbo Pascal taught an entire generation how to think about code, failure, and iteration. It did that through constraints, speed, and ruthless clarity.&lt;/p&gt;
&lt;p&gt;The first shock for modern developers is startup time. Turbo Pascal did not boot with ceremony. It appeared. You opened the IDE, typed, compiled, and got feedback almost instantly. This changed behavior at a deep level. When feedback loops are short, people experiment. They test tiny ideas. They refactor because trying an alternative costs almost nothing. Slow builds do not just waste minutes; they discourage curiosity. Turbo Pascal accidentally optimized curiosity.&lt;/p&gt;
&lt;p&gt;The second shock is the integrated workflow. Editor, compiler, linker, and debugger were not separate worlds stitched together by fragile scripts. They were one coherent environment. Error output was not a scroll of disconnected text; it brought you to the line, in context, immediately. That matters. Good tools reduce the distance between cause and effect. Turbo Pascal reduced that distance aggressively.&lt;/p&gt;
&lt;p&gt;Historically, Borland’s positioning was almost subversive. At a time when serious development tools were expensive and often tied to slower workflows, Turbo Pascal arrived fast and comparatively affordable. That democratized real software creation. Hobbyists could ship utilities. Students could build complete projects. Small consultancies could move quickly without enterprise-sized budgets. This was not just a product strategy; it was a distribution of capability.&lt;/p&gt;
&lt;p&gt;The language itself also helped. Pascal’s structure encouraged readable programs: explicit blocks, strong typing, and a style that pushed developers toward deliberate design rather than accidental scripts that grew wild. In education, that discipline was gold. In practical DOS development, it reduced whole categories of mistakes that were common in looser environments. People sometimes remember Pascal as “academic,” but in Turbo Pascal form it was deeply practical.&lt;/p&gt;
&lt;p&gt;Another underappreciated element was the culture of units. Reusable code packaged in units gave developers a mental model close to modern modular design: separate concerns, publish interfaces, hide implementation details, and reuse tested logic. You felt the architecture, not as a theory chapter, but as something your compiler enforced. If interfaces drifted, builds failed. If dependencies tangled, you noticed immediately. The tool taught architecture by refusing to ignore boundaries.&lt;/p&gt;
&lt;p&gt;Debugging was similarly educational. You stepped through code, watched variables, and saw control flow in a way that made program state tangible. On constrained DOS machines, this was not an abstract “observability platform.” It was intimate and local. You learned what your code &lt;em&gt;actually&lt;/em&gt; did, not what you hoped it did. That habit scales from small Pascal programs to large distributed systems: inspect state, verify assumptions, narrow uncertainty.&lt;/p&gt;
&lt;p&gt;The ecosystem around Turbo Pascal mattered too. Books, magazine listings, BBS uploads, and disk-swapped snippets formed an early social network of practical knowledge. You did not import giant frameworks by default. You copied a unit, read it, understood it, and adapted it. That fostered code literacy. Developers were expected to read source, not just configure dependencies. The result was slower abstraction growth but stronger individual understanding.&lt;/p&gt;
&lt;p&gt;Of course, there were trade-offs. DOS memory models were real pain. Hardware diversity meant edge cases. Portability was weaker than today’s expectations. Yet those constraints produced useful engineering habits: explicit resource budgeting, defensive error handling, and careful initialization order. When you had 640K concerns and no rescue layer above you, discipline was not optional.&lt;/p&gt;
&lt;p&gt;A subtle historical contribution of Turbo Pascal is that it made tooling aesthetics matter. The environment felt intentional. Keyboard-driven operations, predictable menus, and consistent status information created confidence. Good UI for developers is not cosmetic; it changes throughput and cognitive load. Turbo Pascal proved that decades before “developer experience” became a buzzword.&lt;/p&gt;
&lt;p&gt;Why does this still matter? Because many modern teams are relearning the same lessons under different names. We call it “fast feedback,” “inner loop optimization,” “modular design,” “shift-left debugging,” and “operational clarity.” Turbo Pascal users lived these principles daily because the environment rewarded them and punished sloppy alternatives quickly.&lt;/p&gt;
&lt;p&gt;If you revisit Turbo Pascal today, don’t treat it as museum nostalgia. Treat it as instrumentation for your own habits. Notice how quickly you can move with fewer layers. Notice how explicit interfaces reduce surprises. Notice how much easier decisions become when tools expose cause and effect immediately. You may not return to DOS workflows, but you will bring back better instincts.&lt;/p&gt;
&lt;p&gt;In that sense, Turbo Pascal’s legacy is not a language market share story. It is a craft story. It taught people to build small, test often, structure code, and respect constraints. Those are still the foundations of reliable software, whether your target is a DOS executable, a firmware image, or a cloud service spanning continents.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal BGI Tutorial: Dynamic Drivers, Linked Drivers, and Diagnostic Harnesses</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/</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-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/</guid>
      <description>&lt;p&gt;This tutorial gives you a practical BGI workflow that survives deployment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;dynamic driver loading from filesystem&lt;/li&gt;
&lt;li&gt;linked-driver strategy for lower runtime dependency risk&lt;/li&gt;
&lt;li&gt;a minimal diagnostics harness for startup failures&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;preflight-what-you-need&#34;&gt;Preflight: what you need&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Turbo Pascal / Borland Pascal environment with &lt;code&gt;Graph&lt;/code&gt; unit&lt;/li&gt;
&lt;li&gt;one known-good BGI driver set and required &lt;code&gt;.CHR&lt;/code&gt; fonts&lt;/li&gt;
&lt;li&gt;a test machine/profile where paths are not identical to dev directories&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TP5 baseline reminder:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;compile needs &lt;code&gt;GRAPH.TPU&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;runtime needs &lt;code&gt;.BGI&lt;/code&gt; drivers&lt;/li&gt;
&lt;li&gt;stroked fonts need &lt;code&gt;.CHR&lt;/code&gt; files&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;step-1-dynamic-loading-baseline&#34;&gt;Step 1: dynamic loading baseline&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;BGITEST.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 BgiTest;

uses
  Graph, Crt;

var
  gd, gm, gr: Integer;

begin
  gd := Detect;
  InitGraph(gd, gm, &amp;#39;.\BGI&amp;#39;);
  gr := GraphResult;
  Writeln(&amp;#39;Driver=&amp;#39;, gd, &amp;#39; Mode=&amp;#39;, gm, &amp;#39; GraphResult=&amp;#39;, gr);
  if gr &amp;lt;&amp;gt; grOk then
    Halt(1);

  SetColor(15);
  OutTextXY(8, 8, &amp;#39;BGI OK&amp;#39;);
  Rectangle(20, 20, 200, 120);
  ReadKey;
  CloseGraph;
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Expected outcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;with correct path/assets: startup succeeds and simple frame draws&lt;/li&gt;
&lt;li&gt;with missing assets: &lt;code&gt;GraphResult&lt;/code&gt; indicates error and program exits cleanly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Important TP5 behavior: &lt;code&gt;GraphResult&lt;/code&gt; resets to zero after being called. Always
store it to a variable once, then evaluate that value.&lt;/p&gt;
&lt;p&gt;Path behavior detail: if &lt;code&gt;InitGraph(..., PathToDriver)&lt;/code&gt; gets an empty path, the
driver files must be in the current directory.&lt;/p&gt;
&lt;h2 id=&#34;step-2-deployment-discipline-for-dynamic-model&#34;&gt;Step 2: deployment discipline for dynamic model&lt;/h2&gt;
&lt;p&gt;Package checklist:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;executable&lt;/li&gt;
&lt;li&gt;all required &lt;code&gt;.BGI&lt;/code&gt; files for target adapters&lt;/li&gt;
&lt;li&gt;all required &lt;code&gt;.CHR&lt;/code&gt; fonts&lt;/li&gt;
&lt;li&gt;documented runtime path policy&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most &amp;ldquo;BGI bugs&amp;rdquo; are missing files or wrong path assumptions.&lt;/p&gt;
&lt;h2 id=&#34;step-3-linked-driver-strategy-when-you-need-robustness&#34;&gt;Step 3: linked-driver strategy (when you need robustness)&lt;/h2&gt;
&lt;p&gt;Some Borland-era setups support converting/linking BGI driver binaries into
object modules and registering them before &lt;code&gt;InitGraph&lt;/code&gt; (for example through
&lt;code&gt;RegisterBGIdriver&lt;/code&gt; and related registration APIs).&lt;/p&gt;
&lt;p&gt;General workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;run &lt;code&gt;BINOBJ&lt;/code&gt; on &lt;code&gt;.BGI&lt;/code&gt; file(s) to get &lt;code&gt;.OBJ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;link &lt;code&gt;.OBJ&lt;/code&gt; file(s) into program&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;RegisterBGIdriver&lt;/code&gt; before &lt;code&gt;InitGraph&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;InitGraph&lt;/code&gt; and verify &lt;code&gt;GraphResult&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Why teams did this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fewer runtime file dependencies&lt;/li&gt;
&lt;li&gt;simpler deployment to constrained/chaotic DOS installations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tradeoff:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;larger executable and tighter build coupling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ordering constraint from TP5 docs: calling &lt;code&gt;RegisterBGIdriver&lt;/code&gt; after graphics
are already active yields &lt;code&gt;grError&lt;/code&gt; (&lt;code&gt;-11&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If you use &lt;code&gt;InstallUserDriver&lt;/code&gt; with an autodetect callback, TP5 expects that
callback to be a FAR-call function with no parameters returning an integer mode
or &lt;code&gt;grError&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;step-4-diagnostics-harness-you-should-keep-forever&#34;&gt;Step 4: diagnostics harness you should keep forever&lt;/h2&gt;
&lt;p&gt;Keep a dedicated harness separate from game/app engine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prints detected driver/mode and &lt;code&gt;GraphResult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;renders one line, one rectangle, one text string&lt;/li&gt;
&lt;li&gt;exits on keypress&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This lets you quickly answer: &amp;ldquo;is graphics stack alive?&amp;rdquo; before debugging your
full renderer.&lt;/p&gt;
&lt;p&gt;Add one negative test here too: intentionally pass wrong mode for a known
driver and verify expected &lt;code&gt;grInvalidMode&lt;/code&gt; (&lt;code&gt;-10&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&#34;step-5-test-matrix-predict-first-then-run&#34;&gt;Step 5: test matrix (predict first, then run)&lt;/h2&gt;
&lt;p&gt;Define expected outcomes before running each case:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;correct BGI path&lt;/li&gt;
&lt;li&gt;missing driver file&lt;/li&gt;
&lt;li&gt;missing font file&lt;/li&gt;
&lt;li&gt;wrong current directory&lt;/li&gt;
&lt;li&gt;TSR-heavy memory profile&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For each case, record:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;startup status&lt;/li&gt;
&lt;li&gt;exact error code/output&lt;/li&gt;
&lt;li&gt;whether fallback path triggers correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recommended TP5 error codes to classify in logs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grNotDetected&lt;/code&gt; (&lt;code&gt;-2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grFileNotFound&lt;/code&gt; (&lt;code&gt;-3&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grInvalidDriver&lt;/code&gt; (&lt;code&gt;-4&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grNoLoadMem&lt;/code&gt; (&lt;code&gt;-5&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grFontNotFound&lt;/code&gt; (&lt;code&gt;-8&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grNoFontMem&lt;/code&gt; (&lt;code&gt;-9&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grInvalidMode&lt;/code&gt; (&lt;code&gt;-10&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;step-6-fallback-policy-for-production-ish-dos-apps&#34;&gt;Step 6: fallback policy for production-ish DOS apps&lt;/h2&gt;
&lt;p&gt;Never rely on detect-only logic without fallback:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;try preferred mode&lt;/li&gt;
&lt;li&gt;fallback to known-safe mode&lt;/li&gt;
&lt;li&gt;print actionable error if both fail&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A black screen is a product bug, even in retro projects.&lt;/p&gt;
&lt;h2 id=&#34;about-creating-custom-bgi-drivers&#34;&gt;About creating custom BGI drivers&lt;/h2&gt;
&lt;p&gt;Writing full custom BGI drivers is advanced and depends on ABI/tooling details
that are often version-specific and poorly documented. Practical teams usually
ship stock drivers (dynamic or linked) unless there is a hard requirement for
new hardware support.&lt;/p&gt;
&lt;p&gt;If you must go custom, treat it as a separate reverse-engineering project with
its own test harnesses and compatibility matrix.&lt;/p&gt;
&lt;h2 id=&#34;integration-notes-with-overlays-and-memory-strategy&#34;&gt;Integration notes with overlays and memory strategy&lt;/h2&gt;
&lt;p&gt;If graphics startup becomes unstable after enabling overlays:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;verify overlay initialization order&lt;/li&gt;
&lt;li&gt;verify memory headroom before &lt;code&gt;InitGraph&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;test graphics harness independently from overlayed application paths&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This avoids mixing two failure domains during triage.&lt;/p&gt;
&lt;p&gt;Memory interaction note from TP5 docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Graph allocates heap memory for graphics buffer/driver/font paths&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OvrSetBuf&lt;/code&gt; also reshapes memory by shrinking heap&lt;/li&gt;
&lt;li&gt;call order matters (&lt;code&gt;OvrSetBuf&lt;/code&gt; before &lt;code&gt;InitGraph&lt;/code&gt; when both are used)&lt;/li&gt;
&lt;/ul&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-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;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/mode-13h-graphics-in-turbo-pascal/&#34;&gt;Mode 13h Graphics in Turbo Pascal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal History Through Tooling Decisions</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-history-through-tooling/</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-history-through-tooling/</guid>
      <description>&lt;p&gt;People often tell Turbo Pascal history as a sequence of versions and release dates. That timeline matters, but it misses why the tool changed habits so deeply. The real story is tooling ergonomics under constraints: compile speed, predictable output, integrated editing, and a workflow that kept intention intact from keystroke to executable.&lt;/p&gt;
&lt;p&gt;In other words, Turbo Pascal was not only a language product. It was a decision system.&lt;/p&gt;
&lt;h2 id=&#34;why-that-era-felt-so-productive&#34;&gt;Why that era felt so productive&lt;/h2&gt;
&lt;p&gt;The key loop was short and visible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;edit in integrated environment&lt;/li&gt;
&lt;li&gt;compile in seconds&lt;/li&gt;
&lt;li&gt;run immediately&lt;/li&gt;
&lt;li&gt;inspect result and repeat&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No hidden dependency graph. No plugin negotiation. No remote service in the critical path. This reduced context switching in ways modern teams still struggle to recover through process design.&lt;/p&gt;
&lt;p&gt;The historical importance is not nostalgia. It is evidence that feedback-loop economics shape software quality more than fashionable architecture slogans.&lt;/p&gt;
&lt;h2 id=&#34;distribution-shaped-engineering-choices&#34;&gt;Distribution shaped engineering choices&lt;/h2&gt;
&lt;p&gt;In floppy-era ecosystems, distribution size and hardware variability were not side concerns. They drove design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;smaller executables reduced install friction&lt;/li&gt;
&lt;li&gt;deterministic startup mattered on mixed hardware&lt;/li&gt;
&lt;li&gt;clear error paths mattered without telemetry backends&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Turbo Pascal&amp;rsquo;s model rewarded explicit interfaces and compact runtime assumptions. Teams that wanted software to survive wild machine diversity had to be precise.&lt;/p&gt;
&lt;h2 id=&#34;unit-system-as-collaboration-contract&#34;&gt;Unit system as collaboration contract&lt;/h2&gt;
&lt;p&gt;Turbo Pascal units gave teams strong boundaries without heavy ceremony. A unit interface section became a living contract, and the implementation section held the details. This mirrors modern module design principles, but with less boilerplate and fewer moving parts.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;unit ClockFmt;

interface
function IsoTime: string;

implementation
function IsoTime: string;
begin
  IsoTime := &amp;#39;2026-02-22T12:34:56&amp;#39;;
end;

end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Simple pattern, strong effect: contracts became visible and stable.&lt;/p&gt;
&lt;h2 id=&#34;build-behavior-and-trust&#34;&gt;Build behavior and trust&lt;/h2&gt;
&lt;p&gt;One under-discussed historical factor is trust in the build result. Turbo Pascal gave developers strong confidence that what compiled now would run now on the same target profile. This reliability reduced defensive ritual and encouraged experimentation.&lt;/p&gt;
&lt;p&gt;When build systems are unpredictable, teams compensate with process overhead: additional reviews, duplicated staging checks, expanded manual validation. Predictable tooling is not just convenience; it is organizational cost control.&lt;/p&gt;
&lt;h2 id=&#34;debugging-as-craft-not-ceremony&#34;&gt;Debugging as craft, not ceremony&lt;/h2&gt;
&lt;p&gt;Classic debugging in this ecosystem leaned on watch windows, deterministic repro paths, and explicit state inspection. Because the runtime stack was smaller, developers were closer to cause and effect. Failures were painful, but usually legible.&lt;/p&gt;
&lt;p&gt;That legibility is historically important. It built strong mental models in generations of engineers who later carried those habits into network systems, embedded work, and security tooling.&lt;/p&gt;
&lt;h2 id=&#34;what-modern-teams-can-still-steal&#34;&gt;What modern teams can still steal&lt;/h2&gt;
&lt;p&gt;You do not need to abandon modern stacks to learn from this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;optimize for short local feedback loops&lt;/li&gt;
&lt;li&gt;keep module contracts obvious&lt;/li&gt;
&lt;li&gt;reduce hidden build indirection&lt;/li&gt;
&lt;li&gt;separate policy from mechanism in config files&lt;/li&gt;
&lt;li&gt;document assumptions where runtime variability is high&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are the same themes behind &lt;a href=&#34;https://turbovision.in6-addr.net/musings/clarity-is-an-operational-advantage/&#34;&gt;Clarity Is an Operational Advantage&lt;/a&gt; and &lt;a href=&#34;https://turbovision.in6-addr.net/hacking/tools/terminal-kits-for-incident-triage/&#34;&gt;Terminal Kits for Incident Triage&lt;/a&gt;, just seen through retro tooling history.&lt;/p&gt;
&lt;h2 id=&#34;tooling-history-as-systems-history&#34;&gt;Tooling history as systems history&lt;/h2&gt;
&lt;p&gt;Turbo Pascal&amp;rsquo;s relevance endures because it compresses essential engineering lessons into a small environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;architecture is influenced by tool friction&lt;/li&gt;
&lt;li&gt;reliability is influenced by startup discipline&lt;/li&gt;
&lt;li&gt;collaboration quality is influenced by interface clarity&lt;/li&gt;
&lt;li&gt;speed is influenced by feedback-loop latency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those lessons are historical facts and current strategy at the same time.&lt;/p&gt;
&lt;h2 id=&#34;practical-way-to-study-it-now&#34;&gt;Practical way to study it now&lt;/h2&gt;
&lt;p&gt;If you want something concrete, recreate one small project with strict boundaries:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;one executable&lt;/li&gt;
&lt;li&gt;three units max&lt;/li&gt;
&lt;li&gt;explicit config file&lt;/li&gt;
&lt;li&gt;measured compile-run cycle&lt;/li&gt;
&lt;li&gt;one regression checklist file&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then compare your decision speed and bug triage quality against a similar modern project. Treat this as an experiment, not ideology.&lt;/p&gt;
&lt;p&gt;Cross-reference starting points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-in-2025/&#34;&gt;Writing Turbo Pascal in 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-before-the-web/&#34;&gt;Turbo Pascal Before the Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/mode-13h-graphics-in-turbo-pascal/&#34;&gt;Mode 13h Graphics in Turbo Pascal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;History is most useful when it changes present behavior. Turbo Pascal still does that unusually well because the system is small enough to understand and strict enough to teach.&lt;/p&gt;
&lt;p&gt;A useful closing exercise is to measure your own feedback loop in minutes, not feelings. When teams quantify loop time, tooling discussions become clearer and less ideological.&lt;/p&gt;
</description>
    </item>
    
    <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 1: Anatomy and Workflow</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-1-anatomy-and-workflow/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sat, 14 Mar 2026 00:00:00 +0000</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-1-anatomy-and-workflow/</guid>
      <description>&lt;p&gt;Turbo Pascal is remembered for a fast blue IDE, but that is only the surface.
The real strength was a full toolchain with tight feedback loops: editor,
compiler, linker, debugger, units, and predictable artifacts. Part 1 maps that
system in practical terms before we dive into binary formats, overlays, BGI,
and ABI-level language details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Structure map.&lt;/strong&gt; This article proceeds in twelve sections: (1) version and
scope boundaries, (2) toolchain topology and component wiring, (3) artifact
pipeline and engineering signal, (4) IDE options as architecture, (5) directory
and path policy, (6) practical project layout, (7) IDE–CLI parity and
reproducible builds, (8) units as compile boundaries and incremental strategy,
(9) debug loop mechanics and map/debug workflow, (10) external objects and
integration discipline, (11) operational checklists and failure modes, and (12)
how this foundation supports the rest of the series.&lt;/p&gt;
&lt;h2 id=&#34;scope-and-version-boundaries&#34;&gt;Scope and version boundaries&lt;/h2&gt;
&lt;p&gt;When discussing &amp;ldquo;latest Turbo Pascal,&amp;rdquo; engineers usually mean Turbo Pascal 7.0
and, in many setups, Borland Pascal 7 tooling around it. Some executable names
and switches vary by package and installation, so this article uses two rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;describe workflow and architecture in version-stable terms&lt;/li&gt;
&lt;li&gt;call out where command names or options may differ&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That keeps the discussion accurate without pretending all distributions are
identical. TP 5.x used a simpler unit format; TP 6 and 7 extended it with
object-oriented support and richer metadata. Projects that must support both
TP 5 and TP 7 need to avoid OOP extensions and test on both toolchains.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; TP 7 and BP 7 share the same core compiler engine but
differ in packaging: &lt;code&gt;TURBO.EXE&lt;/code&gt; (IDE) vs &lt;code&gt;BP.EXE&lt;/code&gt; (Borland Pascal IDE), and
command-line variants such as &lt;code&gt;TPC.EXE&lt;/code&gt; or &lt;code&gt;BPC.EXE&lt;/code&gt;. The compiler emits
&lt;code&gt;.TPU&lt;/code&gt; (Turbo Pascal Unit) files or &lt;code&gt;.OBJ&lt;/code&gt; for linkable object code; TP 5.x
and TP 6.x used similar conventions with minor format changes. Knowing your
actual binary set (&lt;code&gt;dir *.exe&lt;/code&gt; in the TP install directory) prevents
configuration mistakes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; Version drift between machines—one developer on TP 6, another
on BP 7—manifests as mysterious &amp;ldquo;unit version mismatch&amp;rdquo; or link errors that
do not reproduce elsewhere. &lt;strong&gt;Pitfall:&lt;/strong&gt; assuming &lt;code&gt;TURBO.EXE&lt;/code&gt; and &lt;code&gt;TPC.EXE&lt;/code&gt; on
the same install are always in lockstep; some bundled distributions ship
slightly different compiler builds. &lt;strong&gt;Practical check:&lt;/strong&gt; run &lt;code&gt;tpc -?&lt;/code&gt; (or
equivalent) and note the version string; document it in project setup. If
multiple TP installs exist (e.g. C:\TP and C:\BP), ensure &lt;code&gt;PATH&lt;/code&gt; and project
scripts point to one canonical location to avoid picking up the wrong compiler.&lt;/p&gt;
&lt;h2 id=&#34;toolchain-topology-what-talks-to-what&#34;&gt;Toolchain topology (what talks to what)&lt;/h2&gt;
&lt;p&gt;At minimum, a project involves these moving parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TURBO.EXE&lt;/code&gt; or &lt;code&gt;BP.EXE&lt;/code&gt; style IDE workflow&lt;/li&gt;
&lt;li&gt;command-line compiler (&lt;code&gt;TPC&lt;/code&gt; in many setups)&lt;/li&gt;
&lt;li&gt;linker stage (often via &lt;code&gt;TLINK&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;optional assembler and object modules (&lt;code&gt;TASM&lt;/code&gt; plus &lt;code&gt;.OBJ&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;optional library manager (&lt;code&gt;TLIB&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;dump/inspection tooling (&lt;code&gt;TDUMP&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even if you only press &amp;ldquo;Compile&amp;rdquo; in the IDE, these layers still exist. Knowing
them separately is the difference between &amp;ldquo;works today&amp;rdquo; and &amp;ldquo;I can debug this
under pressure.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; The IDE invokes the compiler internally; the compiler
produces &lt;code&gt;.TPU&lt;/code&gt; or &lt;code&gt;.OBJ&lt;/code&gt; and hands off to &lt;code&gt;TLINK&lt;/code&gt; to produce the final &lt;code&gt;.EXE&lt;/code&gt;.
You rarely invoke TLINK directly—the compiler drives it. Understanding the
handoff helps when TLINK fails: check that all referenced OBJ and TPU files
exist and that no path is wrong.
When you add &lt;code&gt;{$L FASTBLIT}&lt;/code&gt; for an assembly module, the compiler embeds a
call to TLINK with the listed object files. TASM is invoked separately if you
maintain &lt;code&gt;.ASM&lt;/code&gt; sources; TLIB merges &lt;code&gt;.OBJ&lt;/code&gt; into &lt;code&gt;.LIB&lt;/code&gt; archives for reuse.
TDUMP inspects &lt;code&gt;.EXE&lt;/code&gt;, &lt;code&gt;.OBJ&lt;/code&gt;, and &lt;code&gt;.TPU&lt;/code&gt; headers and symbol tables—critical
when a link fails and you need to verify what the compiler actually produced.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build loop semantics.&lt;/strong&gt; Each &amp;ldquo;Compile&amp;rdquo; in the IDE runs the compiler on the
main program; the compiler in turn recompiles any unit whose &lt;code&gt;.PAS&lt;/code&gt; is newer
than its &lt;code&gt;.TPU&lt;/code&gt;, then invokes TLINK. If nothing changed, a second Compile is
effectively a no-op unless you forced a rebuild—but &amp;ldquo;nothing changed&amp;rdquo; depends
on timestamps. Editing a file and reverting without saving leaves the &lt;code&gt;.PAS&lt;/code&gt;
older than the &lt;code&gt;.TPU&lt;/code&gt;, so the compiler skips it. Conversely, touching a unit
file (e.g. via a script) forces recompile even when source is unchanged.
Some installs exposed a &amp;ldquo;Build&amp;rdquo; vs &amp;ldquo;Make&amp;rdquo; distinction: Make recompiles only
changed modules; Build recompiles everything. The command-line &lt;code&gt;tpc&lt;/code&gt; typically
behaves like Make. Knowing which mode you are in avoids confusion when
expectations differ (&amp;ldquo;I changed that!&amp;rdquo; vs &amp;ldquo;it didn&amp;rsquo;t rebuild&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; Debugging a &amp;ldquo;Compiler Error&amp;rdquo; when the real failure is at
link time wastes hours. Learn to read compiler vs linker messages: TP compiler
errors cite source lines; TLINK errors cite missing symbols or object format
issues. When you add &lt;code&gt;{$L file}&lt;/code&gt;, the compiler does not run TASM—you must
assemble &lt;code&gt;.ASM&lt;/code&gt; to &lt;code&gt;.OBJ&lt;/code&gt; yourself. A project using assembly typically has a
two-step build: first &lt;code&gt;tasm /mx module&lt;/code&gt;, then &lt;code&gt;tpc main.pas&lt;/code&gt;. Omitting the
TASM step produces &amp;ldquo;cannot open file&amp;rdquo; or &amp;ldquo;invalid object file&amp;rdquo; from TLINK. &lt;strong&gt;Pitfall:&lt;/strong&gt; the IDE may hide TLINK output or truncate it; a batch build
that echoes full output is essential. &lt;strong&gt;Practical check:&lt;/strong&gt; run a minimal
&lt;code&gt;tpc main.pas&lt;/code&gt; from the command line and observe the exact sequence of
invocations and any warnings; compare with IDE compile to spot divergence.
When TLINK reports &amp;ldquo;undefined symbol,&amp;rdquo; use &lt;code&gt;tdump main.obj | findstr SYMBOL&lt;/code&gt; to
inspect what the compiler actually exported; cross-reference with the unit’s
interface to find mismatches. TDUMP also reveals TPU structure—run
&lt;code&gt;tdump unit.tpu&lt;/code&gt; to see exported symbols and segment names when debugging
circular unit references or missing exports.&lt;/p&gt;
&lt;h2 id=&#34;artifact-pipeline-as-engineering-signal&#34;&gt;Artifact pipeline as engineering signal&lt;/h2&gt;
&lt;p&gt;A typical single-target flow:&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;/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;.PAS  --compile--&amp;gt;  .TPU/.OBJ  --link--&amp;gt;  .EXE
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                              \--optional--&amp;gt; .MAP&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;Extended flows add &lt;code&gt;.OVR&lt;/code&gt; (overlay file), &lt;code&gt;.BGI/.CHR&lt;/code&gt; assets (Graph unit path),
and linked external &lt;code&gt;.OBJ&lt;/code&gt; modules. If output behavior is surprising, artifacts
are your first ground truth, not intuition. Runtime paths for BGI and overlays
must match deployment layout—developing with assets in-project but shipping
an EXE alone causes silent failures at InitGraph or overlay load.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; Each &lt;code&gt;.PAS&lt;/code&gt; file compiles to an intermediate form:
main-program &lt;code&gt;.PAS&lt;/code&gt; → &lt;code&gt;.OBJ&lt;/code&gt; (or directly to &lt;code&gt;.EXE&lt;/code&gt; when TP drives TLINK);
unit &lt;code&gt;.PAS&lt;/code&gt; → &lt;code&gt;.TPU&lt;/code&gt;. The compiler emits one OBJ per main program and one
TPU per unit; the linker then combines them. Multi-module programs (e.g. a
main that uses several units) produce one EXE that embeds all linked code. The linker merges one or more &lt;code&gt;.OBJ&lt;/code&gt; plus referenced
&lt;code&gt;.TPU&lt;/code&gt; content into a single executable. A &lt;code&gt;.MAP&lt;/code&gt; file is produced when you
pass &lt;code&gt;/M&lt;/code&gt; (or equivalent) to the linker—it lists segment layout, public
symbols, and program start address. Overlays (&lt;code&gt;.OVR&lt;/code&gt;) are built separately and
loaded at runtime by the overlay manager.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Map file usage.&lt;/strong&gt; The map lists segments (e.g. &lt;code&gt;CODE&lt;/code&gt;, &lt;code&gt;DATA&lt;/code&gt;, &lt;code&gt;BSS&lt;/code&gt;) with
their load addresses and sizes, followed by a public symbol table with
segment:offset for each symbol. A crash address like &lt;code&gt;0x1234:0x5678&lt;/code&gt; maps to a
routine by finding the segment name, then scanning the symbol list for the
highest address ≤ &lt;code&gt;0x5678&lt;/code&gt; within that segment—that typically identifies the
containing procedure. Segment layout can shift between builds (e.g. when
adding units or changing optimization), so the map must match the exact binary
being debugged. Keep dated copies (&lt;code&gt;MAIN_20260222.MAP&lt;/code&gt;) for shipped builds so
a user crash report from that date can be correlated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; When the program crashes at startup or behaves differently
on another machine, the &lt;code&gt;.MAP&lt;/code&gt; file tells you where symbols landed in
memory—essential for correlating debug output or crash addresses. &lt;strong&gt;Pitfall:&lt;/strong&gt;
stale &lt;code&gt;.TPU&lt;/code&gt; files: a unit’s interface changed but some dependent unit still
compiled against an old &lt;code&gt;.TPU&lt;/code&gt;, producing subtle ABI drift. &lt;strong&gt;Practical check:&lt;/strong&gt;
before release, delete all &lt;code&gt;.TPU&lt;/code&gt; and &lt;code&gt;.OBJ&lt;/code&gt;, rebuild from scratch, and verify
no &amp;ldquo;unit version&amp;rdquo; or &amp;ldquo;identifier not found&amp;rdquo; surprises. For overlay builds, the
&lt;code&gt;.OVR&lt;/code&gt; is produced by a separate invocation; confirm the overlay manager path
matches where you place the &lt;code&gt;.OVR&lt;/code&gt; at runtime.&lt;/p&gt;
&lt;h2 id=&#34;ide-settings-are-architecture-settings&#34;&gt;IDE settings are architecture settings&lt;/h2&gt;
&lt;p&gt;Turbo Pascal options are often treated as editor preferences. They are not.
They directly alter generated code and runtime behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;debug info and symbolic visibility&lt;/li&gt;
&lt;li&gt;optimization strategy&lt;/li&gt;
&lt;li&gt;stack/heap constraints&lt;/li&gt;
&lt;li&gt;runtime checking behavior (range, overflow, I/O)&lt;/li&gt;
&lt;li&gt;code generation assumptions (CPU/FPU target profile)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Disciplined teams freeze these as named build profiles (for example: &lt;code&gt;debug&lt;/code&gt;,
&lt;code&gt;release&lt;/code&gt;, &lt;code&gt;diag&lt;/code&gt;) and log intentional changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; Options like &lt;code&gt;{$D+}&lt;/code&gt; (debug info), &lt;code&gt;{$O+}&lt;/code&gt; (overlay
support), &lt;code&gt;{$R+}&lt;/code&gt; (range checking), and &lt;code&gt;{$S+}&lt;/code&gt; (stack checking) are
compiler directives; the IDE also stores numeric settings (heap size, stack
size, target CPU) in its configuration. These feed into code generation and
linker arguments. A &amp;ldquo;release&amp;rdquo; build typically turns off &lt;code&gt;{$D+}&lt;/code&gt; and &lt;code&gt;{$R+}&lt;/code&gt;,
enables &lt;code&gt;{$O+}&lt;/code&gt; if using overlays, and may bump optimization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; Switching profiles mid-project without documenting the
change leads to &amp;ldquo;works on my machine&amp;rdquo; when one developer runs a debug build
and another ships a release build—different memory layout and checking can
hide or expose bugs. Heap and stack size (configurable in Linker options or via
&lt;code&gt;$M&lt;/code&gt; directive) affect how much data and recursion the program can handle; a
release build with reduced heap may expose allocation failures that a
development build with generous limits never showed. &lt;strong&gt;Pitfall:&lt;/strong&gt; TP stores options in &lt;code&gt;.TP&lt;/code&gt; project files or
in the default configuration; a fresh clone may pick up system defaults instead
of project-specific values. Check-in a &lt;code&gt;.TP&lt;/code&gt; file only if the team agrees;
otherwise, source-level directives are safer and travel with the code. &lt;strong&gt;Practical check:&lt;/strong&gt; maintain a &lt;code&gt;BUILD.CFG&lt;/code&gt; (or
equivalent) or inline directives at the top of &lt;code&gt;MAIN.PAS&lt;/code&gt; that explicitly set
the profile, e.g. &lt;code&gt;{$D+,R+,S+}&lt;/code&gt; for debug and &lt;code&gt;{$D-,R-,S-}&lt;/code&gt; for release. A minimal
&lt;code&gt;BUILD.CFG&lt;/code&gt; can list one directive per line; the compiler reads it before
source. Alternatively, use a single &lt;code&gt;CONFIG.PAS&lt;/code&gt; that each main program and test
&lt;code&gt;uses&lt;/code&gt; first, so the profile is always in version control. The &lt;code&gt;$M&lt;/code&gt; directive
sets stack and heap: &lt;code&gt;{$M stacksize, heapsize, maxheapsize}&lt;/code&gt;. Too-small heap
causes &amp;ldquo;Out of memory&amp;rdquo; at runtime; too-small stack breaks deep recursion or
large local arrays.&lt;/p&gt;
&lt;h2 id=&#34;directory-and-path-policy-where-projects-fail-first&#34;&gt;Directory and path policy (where projects fail first)&lt;/h2&gt;
&lt;p&gt;Most hard-to-reproduce TP failures are path/config drift:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;unit search path differs between machines&lt;/li&gt;
&lt;li&gt;object search path misses external assembly objects&lt;/li&gt;
&lt;li&gt;include path resolves wrong file version&lt;/li&gt;
&lt;li&gt;runtime asset path misses &lt;code&gt;.BGI/.CHR/.OVR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A stable project keeps paths explicit in one place and checks them at startup.
Do not rely on &amp;ldquo;whatever current directory happens to be.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; TP resolves units and includes in a fixed order: current
directory first, then paths from &lt;code&gt;Options | Directories&lt;/code&gt; (or &lt;code&gt;-U&lt;/code&gt; / &lt;code&gt;-I&lt;/code&gt; on the
command line). The order matters: if &lt;code&gt;C:\TP\UNITS&lt;/code&gt; and &lt;code&gt;C:\PROJECT\UNITS&lt;/code&gt; both
exist, whichever is searched first wins. Object files (&lt;code&gt;{$L file}&lt;/code&gt;) are resolved
relative to the source file or the object path. Runtime paths (BGI, fonts) are
handled by the Graph unit and typically use &lt;code&gt;InitGraph&lt;/code&gt;’s driver path or
&lt;code&gt;SetGraphBufSize&lt;/code&gt;; the program must know where its asset directory lives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; A developer who runs TP from &lt;code&gt;C:\PROJECT\SRC&lt;/code&gt; gets different
resolution than one who runs from &lt;code&gt;C:\PROJECT&lt;/code&gt;—units in &lt;code&gt;SRC\&lt;/code&gt; may be found
first, masking a missing path. &lt;strong&gt;Pitfall:&lt;/strong&gt; &lt;code&gt;PATH&lt;/code&gt; and &lt;code&gt;SET&lt;/code&gt; in &lt;code&gt;AUTOEXEC.BAT&lt;/code&gt;
vary by machine; a batch build that does &lt;code&gt;cd \PROJECT\SRC&lt;/code&gt; before invoking
&lt;code&gt;tpc&lt;/code&gt; can behave differently from an IDE launched from a shortcut with a
different working directory. &lt;strong&gt;Practical check:&lt;/strong&gt; add a startup check in &lt;code&gt;MAIN.PAS&lt;/code&gt;
that verifies a known file exists (e.g. &lt;code&gt;ASSETS\BGI\EGAVGA.BGI&lt;/code&gt;) and aborts with
a clear message if not found; document the required directory layout in README.
Use &lt;code&gt;ParamStr(0)&lt;/code&gt; to derive the executable location and build asset paths
relative to it when possible—that helps when the user runs from a different
directory. Example guard at the top of a graphics-heavy main:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{$I-}
assign(f, &amp;#39;ASSETS\BGI\EGAVGA.BGI&amp;#39;);
reset(f);
if IOResult &amp;lt;&amp;gt; 0 then begin
  writeln(&amp;#39;FATAL: BGI path not found. Run from project root.&amp;#39;);
  halt(1);
end;
close(f);
{$I+}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This fails fast instead of letting InitGraph return a cryptic error code.&lt;/p&gt;
&lt;p&gt;TP5 reference details worth remembering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;System&lt;/code&gt; unit is used automatically; other standard units are not.&lt;/li&gt;
&lt;li&gt;non-resident units are resolved by &lt;code&gt;&amp;lt;UnitName&amp;gt;.TPU&lt;/code&gt; search (current dir, then
configured unit directories).&lt;/li&gt;
&lt;li&gt;make/build unit source lookup follows the same pattern with &lt;code&gt;&amp;lt;UnitName&amp;gt;.PAS&lt;/code&gt;.
On the command line, &lt;code&gt;tpc -Upath1;path2 -Ipath3&lt;/code&gt; sets unit and include paths;
semicolon separates multiple entries. Paths are searched in order. Relative
paths are interpreted from the current directory at invoke time—another reason
to standardize &lt;code&gt;cd&lt;/code&gt; before build.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Path resolution behavior.&lt;/strong&gt; &lt;code&gt;{$I filename}&lt;/code&gt; (include) and &lt;code&gt;{$L filename}&lt;/code&gt; (link
object) resolve differently. Include files are searched along the include path
and typically use just the base name (&lt;code&gt;{$I TYPES.INC}&lt;/code&gt;); the compiler merges
the file contents at that point. Object files for &lt;code&gt;{$L}&lt;/code&gt; are usually resolved
relative to the source file&amp;rsquo;s directory first, then the unit/object path.
Using a bare name like &lt;code&gt;{$L FASTBLIT}&lt;/code&gt; assumes &lt;code&gt;FASTBLIT.OBJ&lt;/code&gt; is in the same
directory as the &lt;code&gt;.PAS&lt;/code&gt; or on the object path. A common pitfall: a unit in
&lt;code&gt;SRC\CORE.PAS&lt;/code&gt; with &lt;code&gt;{$L ..\ASM\FASTBLIT}&lt;/code&gt; works when compiled from project
root, but a different working directory can break resolution. Prefer explicit
paths in build configuration (&lt;code&gt;-U&lt;/code&gt;, &lt;code&gt;-I&lt;/code&gt;, object path) over &lt;code&gt;{$L}&lt;/code&gt; with
relative names when the source tree spans multiple directories. Paths
containing spaces (e.g. &lt;code&gt;C:\TP\My Units&lt;/code&gt;) can cause parsing issues in some
older TP installs; stick to 8.3 names in critical paths when possible.&lt;/p&gt;
&lt;h2 id=&#34;practical-project-shape&#34;&gt;Practical project shape&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;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;span class=&#34;lnt&#34;&gt;14
&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;PROJECT/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  SRC/
&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;    CORE.PAS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    RENDER.PAS
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ASM/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    FASTBLIT.ASM
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    FASTBLIT.OBJ
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  BIN/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ASSETS/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    BGI/
&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;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  README.TXT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  CHANGELOG.TXT&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;This looks mundane. That is good. In DOS projects, boring layout is a
stability feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; &lt;code&gt;SRC/&lt;/code&gt; holds all &lt;code&gt;.PAS&lt;/code&gt;; &lt;code&gt;ASM/&lt;/code&gt; holds assembly source
and pre-built &lt;code&gt;.OBJ&lt;/code&gt;; &lt;code&gt;BIN/&lt;/code&gt; receives &lt;code&gt;.EXE&lt;/code&gt;, &lt;code&gt;.OVR&lt;/code&gt;, &lt;code&gt;.MAP&lt;/code&gt;; &lt;code&gt;ASSETS/BGI/&lt;/code&gt; holds
driver and font files. The compiler’s &lt;code&gt;-E&lt;/code&gt; (or equivalent) switch can direct
output to &lt;code&gt;BIN\&lt;/code&gt;. Keeping &lt;code&gt;.TPU&lt;/code&gt; alongside source in &lt;code&gt;SRC\&lt;/code&gt; or in a dedicated
&lt;code&gt;UNITS\&lt;/code&gt; subdirectory avoids polluting the root. A &lt;code&gt;UNITS\&lt;/code&gt; folder with only
TPUs (no PAS) works if you treat it as build output—the batch compile writes
TPUs there and adds &lt;code&gt;-U%CD%\UNITS&lt;/code&gt; so dependents find them. This keeps SRC
clean of generated files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; A flat layout with everything in the project root works
for tiny projects but becomes unmaintainable when units and assets multiply.
&lt;strong&gt;Pitfall:&lt;/strong&gt; storing &lt;code&gt;.TPU&lt;/code&gt; in a shared &lt;code&gt;C:\TP\UNITS&lt;/code&gt; risks cross-project
contamination—two projects with a &lt;code&gt;UTILS&lt;/code&gt; unit will overwrite each other’s
TPU. &lt;strong&gt;Practical check:&lt;/strong&gt; the batch build should &lt;code&gt;cd&lt;/code&gt; to a canonical directory
(e.g. project root), set &lt;code&gt;TPC&lt;/code&gt; output and unit paths explicitly, and produce
deterministic artifacts in &lt;code&gt;BIN\&lt;/code&gt;; &lt;code&gt;dir BIN\*.exe&lt;/code&gt; after build should show
expected output with sensible timestamps. A clean-build target in the batch
helps catch stale-artifact bugs:&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;/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;nl&#34;&gt;clean&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;del&lt;/span&gt; /q SRC\*.TPU &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul
&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;del&lt;/span&gt; /q SRC\*.OBJ &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul
&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;del&lt;/span&gt; /q ASM\*.OBJ &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul
&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;del&lt;/span&gt; /q BIN\*.* &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul
&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; Cleaned
&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;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nl&#34;&gt;eof&lt;/span&gt;&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;Invoke with &lt;code&gt;BUILD.BAT clean&lt;/code&gt; before a release build. If the batch supports
arguments, add &lt;code&gt;if &amp;quot;%1&amp;quot;==&amp;quot;clean&amp;quot; goto clean&lt;/code&gt; at the top so &lt;code&gt;build clean&lt;/code&gt; and
&lt;code&gt;build&lt;/code&gt; both work from a single script.&lt;/p&gt;
&lt;h2 id=&#34;ide-and-cli-parity-is-non-negotiable&#34;&gt;IDE and CLI parity is non-negotiable&lt;/h2&gt;
&lt;p&gt;If a project only builds via hidden IDE state, you do not have a reproducible
build. Keep a batch build path next to the IDE path.&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;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&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;k&#34;&gt;setlocal&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;set&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;MAIN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;SRC\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;c1&#34;&gt;rem command/options vary by TP/BP install; -E directs exe to BIN&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;set&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;TPCDIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;C:\TP
&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;set&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;PATH&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;%TPCDIR%&lt;/span&gt;;&lt;span class=&#34;nv&#34;&gt;%PATH%&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;cd&lt;/span&gt; /d &lt;span class=&#34;nv&#34;&gt;%~dp0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tpc &lt;span class=&#34;nv&#34;&gt;%MAIN%&lt;/span&gt; -U&lt;span class=&#34;nv&#34;&gt;%CD%&lt;/span&gt;\UNITS -EBIN
&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;echo&lt;/span&gt; BUILD OK
&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;end&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;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nl&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;endlocal&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;Technical mechanism.&lt;/strong&gt; &lt;code&gt;tpc&lt;/code&gt; (or &lt;code&gt;bpc&lt;/code&gt;) accepts &lt;code&gt;-U&lt;/code&gt; for unit search path,
&lt;code&gt;-E&lt;/code&gt; for exe output directory, &lt;code&gt;-D&lt;/code&gt; for defines, and &lt;code&gt;-$&lt;/code&gt; for directives.
Exact syntax varies; BP 7 uses &lt;code&gt;-Upath&lt;/code&gt; and &lt;code&gt;-Epath&lt;/code&gt; (no space between switch
and path). The batch file uses &lt;code&gt;cd /d %~dp0&lt;/code&gt; to ensure it runs from the
project root regardless of where it is invoked. Some installs use &lt;code&gt;-Epath&lt;/code&gt; to
send the EXE to a specific directory; without it, the EXE lands next to the
main source, which can clutter &lt;code&gt;SRC\&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; When the IDE build succeeds but the batch fails (or vice
versa), the difference is usually in paths or options. &lt;strong&gt;Pitfall:&lt;/strong&gt; the IDE
may use a different &lt;code&gt;TPC&lt;/code&gt; than the one on &lt;code&gt;PATH&lt;/code&gt; if the shortcut sets its
own environment. &lt;strong&gt;Practical check:&lt;/strong&gt; add &lt;code&gt;tpc %MAIN% 2&amp;gt;&amp;amp;1 | more&lt;/code&gt; to capture
full compiler/linker output; compare character-for-character with IDE compile
log if behavior diverges. Expected outcome: success yields deterministic &lt;code&gt;.EXE&lt;/code&gt;
in &lt;code&gt;BIN\&lt;/code&gt;; failure yields non-zero exit and repeatable error output.&lt;/p&gt;
&lt;h2 id=&#34;units-are-compile-boundaries-not-just-reuse&#34;&gt;Units are compile boundaries, not just reuse&lt;/h2&gt;
&lt;p&gt;Units define contracts and incremental rebuild boundaries. This yields two
benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;interface changes produce immediate compile-time blast radius&lt;/li&gt;
&lt;li&gt;implementation-only changes stay local when boundaries are clean&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That behavior gives architectural feedback automatically. If tiny edits trigger
massive recompilation or link churn, boundaries are weak.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; A unit’s &lt;code&gt;interface&lt;/code&gt; section is compiled first and
emitted into the &lt;code&gt;.TPU&lt;/code&gt;; dependents read that interface. Changing the
interface (adding/removing/altering exported declarations) invalidates all
dependent units—they must recompile. Changing only the &lt;code&gt;implementation&lt;/code&gt;
invalidates only that unit’s TPU. The compiler tracks dependency via timestamps
(or explicit make rules) and recompiles only what changed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; A well-factored project compiles quickly during
development: edit one unit’s implementation, only that unit rebuilds.
Interface changes are expensive by design—they force you to confront coupling.
&lt;strong&gt;Pitfall:&lt;/strong&gt; large &amp;ldquo;god&amp;rdquo; units with sprawling interfaces cause rebuild cascades;
splitting into smaller units with narrow interfaces reduces blast radius.
&lt;strong&gt;Practical check:&lt;/strong&gt; run a clean build, make a one-line implementation change,
rebuild—only that unit’s TPU should change. If half the project rebuilds,
revisit boundaries. &lt;strong&gt;Incremental compile strategy:&lt;/strong&gt; without make, TP recompiles
a unit when its &lt;code&gt;.PAS&lt;/code&gt; is newer than its &lt;code&gt;.TPU&lt;/code&gt;. Compile in dependency order
(leaf units first) or rely on &lt;code&gt;uses&lt;/code&gt; order; some teams kept a batch that
compiled units explicitly before the main program to avoid timestamp quirks.
See also: &lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-units-as-architecture/&#34;&gt;Turbo Pascal Units as Architecture, Not Just
Reuse&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;debug-loop-mechanics&#34;&gt;Debug loop mechanics&lt;/h2&gt;
&lt;p&gt;A strong TP debugging loop is short and explicit:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;define expected behavior before run&lt;/li&gt;
&lt;li&gt;run the same deterministic input&lt;/li&gt;
&lt;li&gt;inspect state at subsystem boundaries&lt;/li&gt;
&lt;li&gt;adjust one variable or one assumption&lt;/li&gt;
&lt;li&gt;rerun same case&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fast compile-run cycles make this practical dozens of times per hour. That is
why teams felt productive: not because bugs were fewer, but because feedback
latency stayed low.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; TP’s integrated debugger uses &lt;code&gt;{$D+}&lt;/code&gt; (debug info) and
&lt;code&gt;{$L+}&lt;/code&gt; (local symbol info) to map source lines to addresses. The linker’s map
file (&lt;code&gt;/M&lt;/code&gt; or &lt;code&gt;$M&lt;/code&gt; output) lists segment:offset for public symbols. When a
crash occurs at a hex address, you look up that address in the map to identify
the routine. TD (Turbo Debugger) can attach to a running process or launch
the program with breakpoints; TD requires the same debug info and matching
source paths.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; A typical cycle: set breakpoint in TD, run, inspect
variables, fix source, recompile, run again. TD can be launched from the
command line with &lt;code&gt;td main.exe&lt;/code&gt; or from the IDE’s Run menu; ensure the
working directory is set so the program finds its assets. Without a map file, a crash dump
(e.g. from a user) is useless—you cannot map the fault address back to a
function.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Map/debug workflow.&lt;/strong&gt; When a user reports &amp;ldquo;it crashed at 1234:5678,&amp;rdquo; the
workflow is: (1) obtain the exact EXE they ran—rebuilding from &amp;ldquo;same source&amp;rdquo;
may produce different segment layout; (2) ensure you have the matching map
from that build; (3) parse the address: segment 1234 hex, offset 5678 hex;
(4) open the map, locate the segment (often &lt;code&gt;CODE&lt;/code&gt; or &lt;code&gt;C0&lt;/code&gt;), find the symbol
with the largest address ≤ 5678 in that segment—that is the containing
routine; (5) open that routine in the source and reason about what could fault
at that offset. TD&amp;rsquo;s &amp;ldquo;View | CPU&amp;rdquo; shows disassembly; correlating the fault
address with the map gives you the Pascal routine to inspect. If debug info
was stripped (release build), you still have the map for symbol-level
localization; line numbers require &lt;code&gt;{$D+}&lt;/code&gt; and &lt;code&gt;{$L+}&lt;/code&gt; in the binary. Some
teams kept a post-build step that copied &lt;code&gt;MAIN.EXE&lt;/code&gt; and &lt;code&gt;MAIN.MAP&lt;/code&gt; to a
&lt;code&gt;RELEASE\&lt;/code&gt; folder with a date suffix, so crash reports could be matched to
archived symbol data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pitfall:&lt;/strong&gt; debug builds with &lt;code&gt;{$D+}&lt;/code&gt; produce larger executables
and slightly different code layout; a bug that appears only in release may be
a timing or memory-layout issue. &lt;strong&gt;Practical check:&lt;/strong&gt; keep a debug build
profile that always generates &lt;code&gt;.MAP&lt;/code&gt;, and ensure your run script or batch uses
that profile when investigating crashes. Example map lookup: &lt;code&gt;findstr /C:&amp;quot;RoutineName&amp;quot; MAIN.MAP&lt;/code&gt; to locate a symbol’s segment. &lt;strong&gt;Team checklist:&lt;/strong&gt; (1)
every developer runs &lt;code&gt;tpc -?&lt;/code&gt; and records version in project docs; (2) new
machines run a clean build before first commit; (3) before release, one
developer performs a memory-stressed boot (load COMMAND.COM, a few TSRs, then
run) to catch conventional-memory edge cases. (4) When integrating assembly or C
modules, one person owns the calling-convention doc and reviews any new external
declarations. (5) Archive the exact &lt;code&gt;BUILD.BAT&lt;/code&gt; and &lt;code&gt;BUILD.CFG&lt;/code&gt; (or equivalent)
with each shipped build so you can reproduce it later.&lt;/p&gt;
&lt;h2 id=&#34;external-objects-from-day-one&#34;&gt;External objects from day one&lt;/h2&gt;
&lt;p&gt;Many real projects mixed Pascal with assembly or C object modules. Keep that
integration explicit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;source ownership (&lt;code&gt;.ASM&lt;/code&gt;/&lt;code&gt;.PAS&lt;/code&gt;) is documented&lt;/li&gt;
&lt;li&gt;object generation step is reproducible&lt;/li&gt;
&lt;li&gt;calling convention assumptions are written next to declarations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Technical mechanism.&lt;/strong&gt; &lt;code&gt;{$L FASTBLIT}&lt;/code&gt; tells the compiler to pass
&lt;code&gt;FASTBLIT.OBJ&lt;/code&gt; to the linker. TP uses Pascal calling convention (left-to-right
push, caller clears stack) and specific name mangling; assembly routines must
match. A typical declaration:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{$L FASTBLIT}
procedure FastBlit(Src, Dst: pointer; Count: word); external;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;.OBJ&lt;/code&gt; is resolved from the current directory or object path. TASM
assembles &lt;code&gt;FASTBLIT.ASM&lt;/code&gt; with &lt;code&gt;tasm /mx fastblit&lt;/code&gt; (case-sensitive symbols)
to produce the object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Object integration guardrails.&lt;/strong&gt; When a unit uses &lt;code&gt;{$L MODULE}&lt;/code&gt;, that unit
must link before any unit or main program that imports it—the compiler passes
OBJ references through to TLINK in use order. If &lt;code&gt;MAIN&lt;/code&gt; uses &lt;code&gt;CORE&lt;/code&gt; and &lt;code&gt;CORE&lt;/code&gt;
uses &lt;code&gt;{$L FASTBLIT}&lt;/code&gt;, the linker receives &lt;code&gt;CORE.OBJ&lt;/code&gt; (from CORE&amp;rsquo;s TPU) plus
&lt;code&gt;FASTBLIT.OBJ&lt;/code&gt;; MAIN&amp;rsquo;s OBJ comes last. A missing &lt;code&gt;FASTBLIT.OBJ&lt;/code&gt; produces
TLINK &amp;ldquo;cannot open file&amp;rdquo; or &amp;ldquo;invalid object file&amp;rdquo;—the compiler does not
pre-validate &lt;code&gt;{$L}&lt;/code&gt; references. Guardrail: run a pre-build step that checks
all &lt;code&gt;{$L}&lt;/code&gt;-referenced OBJs exist before invoking &lt;code&gt;tpc&lt;/code&gt;. If a unit exports a
procedure declared &lt;code&gt;external&lt;/code&gt;, the OBJ must export a matching public symbol
(fastblit, FASTBLIT, or whatever your assembler emits); &lt;code&gt;tdump unit.obj&lt;/code&gt;
shows the actual exports. Mismatched symbol names cause &amp;ldquo;undefined symbol&amp;rdquo; at
link time. When mixing TP units with C object files, the C module must use
the correct calling convention (&lt;code&gt;pascal&lt;/code&gt; or &lt;code&gt;cdecl&lt;/code&gt; as documented) and
export names that match the Pascal &lt;code&gt;external&lt;/code&gt; declaration; C&amp;rsquo;s default name
mangling does not match TP&amp;rsquo;s expectations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow impact.&lt;/strong&gt; Adding an external module without documenting convention
leads to subtle stack corruption or wrong arguments. &lt;strong&gt;Pitfall:&lt;/strong&gt; mixing TP’s
default calling convention with C’s cdecl or fastcall from a C-compiled &lt;code&gt;.OBJ&lt;/code&gt;
causes unpredictable behavior. &lt;strong&gt;Practical check:&lt;/strong&gt; add a &lt;code&gt;BUILD_ASM.BAT&lt;/code&gt; that
runs &lt;code&gt;tasm&lt;/code&gt; on all &lt;code&gt;.ASM&lt;/code&gt; files and fails if any object is missing; invoke it
from the main build or document it as a prerequisite. Document the expected
object-file location (ASM, SRC, or a shared OBJ lib) so new contributors know
where to put compiled assembly. Part 2 goes deep on this, including object/module
investigation and symbol diagnostics.&lt;/p&gt;
&lt;h2 id=&#34;operational-checklists-that-saved-teams&#34;&gt;Operational checklists that saved teams&lt;/h2&gt;
&lt;p&gt;Before shipping any build profile:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;clean rebuild from source (no stale artifacts)&lt;/li&gt;
&lt;li&gt;confirm expected files (&lt;code&gt;.EXE&lt;/code&gt;, optional &lt;code&gt;.OVR&lt;/code&gt;, BGI assets)&lt;/li&gt;
&lt;li&gt;compare binary size/checksum against previous known-good&lt;/li&gt;
&lt;li&gt;run one memory-stressed boot profile test&lt;/li&gt;
&lt;li&gt;archive build settings with artifact&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is primitive CI and still effective. A minimal pre-ship batch can automate
steps 1–3:&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;/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;k&#34;&gt;call&lt;/span&gt; BUILD.BAT clean
&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;call&lt;/span&gt; BUILD.BAT
&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;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nl&#34;&gt;eof&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;dir&lt;/span&gt; BIN\*.EXE
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fc BIN\MAIN.EXE C:\RELEASE\MAIN.EXE&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;code&gt;fc&lt;/code&gt; compares current build to last known-good; manual review of any diff
prevents accidental regression.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reproducibility patterns.&lt;/strong&gt; To reproduce a build months later: (1) archive
the exact &lt;code&gt;BUILD.BAT&lt;/code&gt;, &lt;code&gt;BUILD.CFG&lt;/code&gt;, and any &lt;code&gt;CONFIG.PAS&lt;/code&gt; or directive files
with each release; (2) record the compiler version (&lt;code&gt;tpc -?&lt;/code&gt; output) in
CHANGELOG or a &lt;code&gt;BUILD_INFO.TXT&lt;/code&gt;; (3) avoid relying on &lt;code&gt;date&lt;/code&gt;/&lt;code&gt;time&lt;/code&gt; inside
binaries if you need bit-identical output—some linkers embed timestamps.
Clean builds from the same source with the same toolchain should produce
functionally identical executables; exact byte-for-byte match may require
controlling timestamp and path variables. When debugging &amp;ldquo;works on build
machine, fails elsewhere,&amp;rdquo; compare the full &lt;code&gt;tpc&lt;/code&gt; command line, &lt;code&gt;PATH&lt;/code&gt;, and
current directory between environments. A &lt;code&gt;BUILD_VERBOSE.BAT&lt;/code&gt; that echoes
&lt;code&gt;%PATH%&lt;/code&gt;, &lt;code&gt;cd&lt;/code&gt;, and the exact &lt;code&gt;tpc&lt;/code&gt; invocation helps document the winning
configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Realistic failure modes.&lt;/strong&gt; (a) Stale TPU: a unit was changed but an old TPU
remained; symptoms include &amp;ldquo;identifier not found&amp;rdquo; at link or runtime behavior
that contradicts the source. (b) Path drift: unit or object path wrong; &amp;ldquo;Cannot
find unit X&amp;rdquo; or &amp;ldquo;Undefined symbol.&amp;rdquo; (c) Config mismatch: release build with
debug assertions left on, or wrong overlay flags. (d) Asset missing: BGI or
OVR not in expected path; InitGraph or overlay load fails at runtime. (e)
Memory: loading with different TSRs or drivers changes free conventional memory;
a marginal program may work in one boot and fail in another. (f) Optimization:
aggressive optimization can reorder or eliminate code; a bug that disappears
with &lt;code&gt;{$O-}&lt;/code&gt; is often a race or uninitialized variable exposed by different
layout. &lt;strong&gt;Troubleshooting patterns.&lt;/strong&gt; For &amp;ldquo;unit version mismatch&amp;rdquo; or odd link errors:
delete all &lt;code&gt;.TPU&lt;/code&gt; and &lt;code&gt;.OBJ&lt;/code&gt;, rebuild from scratch. Record the exact command
line and paths that produced the failing build—often the fix is a path typo or
missing &lt;code&gt;-U&lt;/code&gt; rather than a source bug. For runtime path failures:
add a diagnostic that prints &lt;code&gt;ParamStr(0)&lt;/code&gt; and the path it derives for assets.
For &amp;ldquo;works on my machine&amp;rdquo;: compare &lt;code&gt;mem&lt;/code&gt; output, &lt;code&gt;path&lt;/code&gt;, and &lt;code&gt;set&lt;/code&gt; between
machines; document minimal boot config. For crash-with-no-symbols: ensure
debug build produces &lt;code&gt;.MAP&lt;/code&gt; and that you have the exact source revision that
built the crashing binary. &lt;strong&gt;Reproduction kit:&lt;/strong&gt; when a user reports a crash,
ask for (1) the exact EXE they ran, (2) &lt;code&gt;mem&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt; output, (3) steps to
reproduce. Rebuild from tagged source, run under TD with the same input, and
use the map to set breakpoints near the fault address.&lt;/p&gt;
&lt;h2 id=&#34;why-this-part-matters-for-the-rest-of-the-series&#34;&gt;Why this part matters for the rest of the series&lt;/h2&gt;
&lt;p&gt;Parts 2 to 5 assume you understand this topology. Without it, TPU forensics,
overlay policy, and BGI packaging all look like isolated tricks. They are not.
They are consequences of one coherent pipeline. Part 2’s object and unit
investigation relies on knowing how TPU and OBJ flow into the linker; overlay
tutorials presume you manage paths and artifact placement; BGI packaging
assumes asset paths and runtime resolution. A disciplined build loop and
checklist habit pays off when those advanced topics introduce new failure modes.
New contributors should complete the operational checklist once manually before
relying on automation—the exercise builds intuition for what can go wrong and
where to look when it does. Parts 3–5 (overlays, BGI, ABI) each add new
artifact types and path requirements; the habits established here—clean builds,
explicit paths, archived config—scale to those more complex setups.&lt;/p&gt;
&lt;p&gt;Next:&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-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;
&lt;p&gt;Related deep dives:&lt;/p&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/turbo-pascal-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/&#34;&gt;Turbo Pascal BGI Tutorial: Dynamic Drivers, Linked Drivers, and Diagnostic Harnesses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-in-2025/&#34;&gt;Writing Turbo Pascal in 2025&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal Toolchain, Part 2: Objects, Units, and Binary Investigation</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-2-objects-units-and-binary-investigation/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sat, 14 Mar 2026 12:00:00 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-2-objects-units-and-binary-investigation/</guid>
      <description>&lt;p&gt;Part 1 covered workflow. Part 2 goes where practical debugging starts: the
actual artifacts on disk. In Turbo Pascal, build failures and runtime bugs are
often solved faster by reading files and link maps than by re-reading source.
The tools are simple—TDUMP, MAP files, &lt;code&gt;strings&lt;/code&gt;, hex diffs—but used
systematically they turn &amp;ldquo;it used to work&amp;rdquo; into &amp;ldquo;here is exactly what
changed.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Structure map.&lt;/strong&gt; This article proceeds in eleven sections: (1) artifact
catalog and operational meaning, (2) TP5 unit-resolution behavior, (3) TPU
constraints and version coupling, (4) TPU differential forensics and
reconstruction when source is missing, (5) OBJ/LIB forensics and OMF orientation,
(6) MAP file workflow and TDUMP-style inspection loops, (7) EXE-level checks
before deep disassembly, (8) external OBJ integration and calling-convention
cautions, (9) repeatable troubleshooting matrix with high-signal checks, (10)
manipulating artifacts safely and team discipline for reproducibility, and
(11) unit libraries and cross references.&lt;/p&gt;
&lt;h2 id=&#34;artifact-catalog-with-operational-meaning&#34;&gt;Artifact catalog with operational meaning&lt;/h2&gt;
&lt;p&gt;Typical TP/BP project artifacts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.PAS&lt;/code&gt;: Pascal source (program or unit)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.TPU&lt;/code&gt;: compiled unit (compiler-consumable binary module)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.OBJ&lt;/code&gt;: object module (often OMF format)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.LIB&lt;/code&gt;: archive of &lt;code&gt;.OBJ&lt;/code&gt; modules&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.EXE&lt;/code&gt;/&lt;code&gt;.COM&lt;/code&gt;: linked executable&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.MAP&lt;/code&gt;: linker map with symbol/segment addresses&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.OVR&lt;/code&gt;: overlay file (if overlay build path is enabled)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.BGI&lt;/code&gt;/&lt;code&gt;.CHR&lt;/code&gt;: Graph unit driver/font assets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This list is not trivia. It is your debugging map. OVR files are loaded at runtime
when overlay code executes; if the OVR path is wrong or the file is missing, the
program may hang or crash on overlay entry rather than at startup. BGI and CHR
are resolved by path at runtime—Graph unit &lt;code&gt;InitGraph&lt;/code&gt; searches the driver path.
Capture these paths in your environment documentation; &amp;ldquo;works here, fails there&amp;rdquo;
often traces to BGI/OVR path differences.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tool availability.&lt;/strong&gt; TDUMP ships with Borland toolchains; if missing,
&lt;code&gt;omfdump&lt;/code&gt; (from the OMFutils project) or &lt;code&gt;objdump&lt;/code&gt; with appropriate flags
can suffice for OBJ/LIB inspection, though output format differs. On modern
systems, &lt;code&gt;strings&lt;/code&gt; and &lt;code&gt;hexdump&lt;/code&gt; are standard. The workflows described here
assume TDUMP is available; adapt commands if using substitutes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inspection tool mapping.&lt;/strong&gt; Each artifact type has a primary inspection path:
TPU → &lt;code&gt;strings&lt;/code&gt;, &lt;code&gt;hexdump&lt;/code&gt;, or compiler re-compile test; OBJ/LIB/EXE →
&lt;code&gt;TDUMP&lt;/code&gt;; MAP → diff against baseline. When troubleshooting, pick the artifact
closest to the failure and work outward. Link failures start at OBJ/LIB; unit
mismatch starts at TPU; runtime crashes may need EXE + MAP to correlate
addresses with symbols.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artifact dependency graph.&lt;/strong&gt; A program&amp;rsquo;s build products form a directed
graph: sources (&lt;code&gt;.PAS&lt;/code&gt;, &lt;code&gt;.ASM&lt;/code&gt;) produce TPU/OBJ; those plus linker input
produce EXE; optional MAP records the link result. When a failure occurs,
identify which edge of this graph is broken. &amp;ldquo;Compile works, link fails&amp;rdquo; means
the TPU→EXE or OBJ→EXE edge; &amp;ldquo;link works, crash on startup&amp;rdquo; means the EXE
itself or its runtime dependencies (BGI, OVR, paths). Staying aware of the
graph prevents conflating compile-time and link-time issues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Regression triage.&lt;/strong&gt; When a previously working build starts failing, the
fastest diagnostic is a binary diff: compare the new MAP and EXE (or checksums)
to the last known-good. If the MAP is identical, the problem is environmental
(paths, runtime, machine). If the MAP changed, the regression is in the
build; then compare OBJ/TPU timestamps to see which module changed. This
two-step filter—build vs environment, then which module—cuts investigation
time dramatically.&lt;/p&gt;
&lt;h2 id=&#34;tp5-unit-resolution-behavior-manual-grounded&#34;&gt;TP5 unit-resolution behavior (manual-grounded)&lt;/h2&gt;
&lt;p&gt;Turbo Pascal 5.0 describes a concrete unit lookup order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;check resident units loaded from &lt;code&gt;TURBO.TPL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;if not resident, search &lt;code&gt;&amp;lt;UnitName&amp;gt;.TPU&lt;/code&gt; in current directory&lt;/li&gt;
&lt;li&gt;then search configured unit directories (&lt;code&gt;/U&lt;/code&gt; or IDE Unit Directories)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For make/build flows that compile unit sources, &lt;code&gt;&amp;lt;UnitName&amp;gt;.PAS&lt;/code&gt; follows the
same directory search pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Path-order trap.&lt;/strong&gt; If &lt;code&gt;CORE.TPU&lt;/code&gt; exists in both the current directory and a
configured unit path, the first match wins. Two developers with different
path or unit-dir settings can compile &amp;ldquo;the same&amp;rdquo; project and get different
TPUs. Fix: use a single canonical unit directory and document it in
&lt;code&gt;BUILD.BAT&lt;/code&gt; or &lt;code&gt;README&lt;/code&gt;. Resident units from &lt;code&gt;TURBO.TPL&lt;/code&gt; bypass file search;
updating a &lt;code&gt;.TPU&lt;/code&gt; on disk has no effect if the resident copy is used. For
custom units, use non-resident layout so you control the artifact.&lt;/p&gt;
&lt;h2 id=&#34;tpu-reality-powerful-version-coupled-poorly-documented&#34;&gt;TPU reality: powerful, version-coupled, poorly documented&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.TPU&lt;/code&gt; is a compiled unit format designed for compiler/linker consumption, not
for human readability. Two facts matter in practice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;TPUs are tightly tied to compiler version/family. TP5 TPUs are not
guaranteed compatible with TP6 or BP7; even minor compiler bumps can change
internal layout.&lt;/li&gt;
&lt;li&gt;Mixing stale or cross-version TPUs causes misleading failures: &amp;ldquo;unit version
mismatch,&amp;rdquo; phantom unresolved externals, or runtime corruption that does not
correlate with recent edits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Version-pinning rule: lock the compiler and RTL version for a project and do
not mix TPUs built by different compilers. If migrating, rebuild all units from
source under the new toolchain rather than reusing old TPUs.&lt;/p&gt;
&lt;p&gt;Important honesty point: I cannot verify a complete, official, stable
byte-level specification for late TPU variants in this repo. Practical
reverse-engineering material exists, but fields and layout differ by version.
So treat any fixed &amp;ldquo;TPU format diagram&amp;rdquo; from random sources as version-scoped,
not universal.&lt;/p&gt;
&lt;h2 id=&#34;tpu-differential-forensics-high-signal-technique&#34;&gt;TPU differential forensics (high signal technique)&lt;/h2&gt;
&lt;p&gt;When format docs are weak, compare binaries under controlled source changes.&lt;/p&gt;
&lt;p&gt;Recommended experiment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;compile baseline unit and save &lt;code&gt;U0.TPU&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;change implementation only, compile &lt;code&gt;U1.TPU&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;change interface signature, compile &lt;code&gt;U2.TPU&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;compare byte-level deltas (&lt;code&gt;fc /b&lt;/code&gt; or hex diff tool)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Expected outcomes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;implementation-only changes affect localized regions (code blocks, constants)&lt;/li&gt;
&lt;li&gt;interface changes tend to alter broader metadata/signature regions and may
shift offsets used by dependent units&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Concrete example: if you add one procedure to an interface, dependent units
that &lt;code&gt;uses&lt;/code&gt; it must be recompiled. The TPU header/symbol tables change; a
stale dependent TPU can produce &amp;ldquo;unit version mismatch&amp;rdquo; or subtle ABI drift.
Always keep the forensics baseline (&lt;code&gt;U0.TPU&lt;/code&gt;) immutable; copy, don&amp;rsquo;t overwrite.&lt;/p&gt;
&lt;p&gt;When comparing deltas, focus on regions near the start (header/metadata) versus
the tail (code and data blocks). Interface changes often perturb both; pure
implementation changes usually leave the header stable and alter only later
regions. If a delta spans many disjoint areas, treat the unit as incompatible
with prior dependents and schedule a full recompile. This gives practical
understanding of compatibility sensitivity without relying on undocumented
magic constants.&lt;/p&gt;
&lt;h2 id=&#34;what-to-do-when-you-only-have-a-tpu-no-source&#34;&gt;What to do when you only have a TPU (no source)&lt;/h2&gt;
&lt;p&gt;This is a common retro-maintenance scenario.&lt;/p&gt;
&lt;h3 id=&#34;step-1-classify-before-touching-code&#34;&gt;Step 1: classify before touching code&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;identify likely compiler generation (project docs, timestamps, known toolchain)&lt;/li&gt;
&lt;li&gt;keep original TPU immutable (copy to &lt;code&gt;forensics/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;confirm build environment matches expected compiler generation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wrong compiler often produces &amp;ldquo;unit format error&amp;rdquo; or similar before any useful
diagnostic. If you have multiple TP versions installed, ensure &lt;code&gt;PATH&lt;/code&gt; and
invocation point at the correct one.&lt;/p&gt;
&lt;h3 id=&#34;step-2-inspect-for-recoverable-metadata&#34;&gt;Step 2: inspect for recoverable metadata&lt;/h3&gt;
&lt;p&gt;Use lightweight inspection first:&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;/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-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;strings SOMEUNIT.TPU &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; less
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;hexdump -C SOMEUNIT.TPU &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; less&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;Expected outcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;discover symbol-like names or error strings&lt;/li&gt;
&lt;li&gt;estimate whether unit contains useful identifiers or is mostly opaque&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If identifiers are absent, you still can treat the unit as a black-box provider.&lt;/p&gt;
&lt;h3 id=&#34;step-3-reconstruct-interface-incrementally&#34;&gt;Step 3: reconstruct interface incrementally&lt;/h3&gt;
&lt;p&gt;If you know or infer exported symbols, create a probe unit/program and compile
against the TPU using conservative declarations. Iterate by compiler feedback:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;declare one procedure/function candidate&lt;/li&gt;
&lt;li&gt;compile&lt;/li&gt;
&lt;li&gt;fix signature assumptions from diagnostics&lt;/li&gt;
&lt;li&gt;repeat&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is slow and effective. Think of it as ABI archaeology, not decompilation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No-source caveat.&lt;/strong&gt; Reconstructing an interface from a TPU alone is
best-effort. Some identifiers may be mangled or stripped; constant values and
exact type layouts are harder to recover. When in doubt, treat the unit as
opaque and call only what you can confirm compiles and behaves correctly.
Do not assume undocumented TPU layout is stable across compiler versions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Recovery priority.&lt;/strong&gt; If you have partial source (e.g. one unit&amp;rsquo;s &lt;code&gt;.PAS&lt;/code&gt; but
not its dependencies), compile that first and see what the compiler reports as
missing. The error messages often reveal needed unit or symbol names. Work
from known-good declarations inward; avoid guessing large interface blocks from
scratch when you can narrow the surface with compiler feedback.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version-scoping of claims.&lt;/strong&gt; The TPU layout and OMF record details
described here are based on commonly observed behavior in TP5/BP7-era
toolchains. Tool variants (TASM vs MASM, TLINK vs other linkers) can produce
slightly different OBJ/LIB layouts. Where this article makes format-specific
claims, treat them as applicable to the Borland toolchain family; other
environments may differ.&lt;/p&gt;
&lt;h2 id=&#34;obj-and-lib-forensics-where-link-truth-lives&#34;&gt;OBJ and LIB forensics: where link truth lives&lt;/h2&gt;
&lt;p&gt;When external modules are involved, &lt;code&gt;.OBJ&lt;/code&gt; and &lt;code&gt;.LIB&lt;/code&gt; are usually where truth
is found. In many Borland-era environments, object modules follow OMF records;
you can inspect structure with &lt;code&gt;TDUMP&lt;/code&gt; or compatible tools (e.g. &lt;code&gt;omfdump&lt;/code&gt;,
&lt;code&gt;objdump&lt;/code&gt; with OMF support where available).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Basic inspection workflow:&lt;/strong&gt;&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;/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;tdump FASTBLIT.OBJ &lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; FASTBLIT.DMP
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tdump RUNTIME.LIB &lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; RUNTIME.DMP
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tdump MAIN.EXE &lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; MAIN.DMP&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;For &lt;code&gt;.LIB&lt;/code&gt; files, TDUMP lists contained object modules and their publics. For
&lt;code&gt;.OBJ&lt;/code&gt; files, you see the single module&amp;rsquo;s records. For &lt;code&gt;.EXE&lt;/code&gt; files, you see
the linked image and segment layout.&lt;/p&gt;
&lt;p&gt;In dumps, you are looking for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;exported/public symbol names (exact spelling and decoration, if any)&lt;/li&gt;
&lt;li&gt;unresolved externals expected from other modules&lt;/li&gt;
&lt;li&gt;segment/class patterns that do not match expectations (e.g. &lt;code&gt;CODE&lt;/code&gt; vs
&lt;code&gt;CSEG&lt;/code&gt;, &lt;code&gt;FAR&lt;/code&gt; vs &lt;code&gt;NEAR&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If names look right but link still fails, calling convention or far/near model
mismatch is often the real issue.&lt;/p&gt;
&lt;p&gt;Manual anchor: TP5 external declarations are linked through &lt;code&gt;{$L filename}&lt;/code&gt;.
This is documented as the assembly-language interop path for &lt;code&gt;external&lt;/code&gt;
subprogram declarations. The linker searches object directories when path is
not explicit; document that search order for your setup.&lt;/p&gt;
&lt;h3 id=&#34;omf-record-level-orientation-why-tdump-output-matters&#34;&gt;OMF record-level orientation (why TDUMP output matters)&lt;/h3&gt;
&lt;p&gt;You will often see record classes such as module header (&lt;code&gt;THEADR&lt;/code&gt;), external
definitions (&lt;code&gt;EXTDEF&lt;/code&gt;), public definitions (&lt;code&gt;PUBDEF&lt;/code&gt;), communal definitions
(&lt;code&gt;COMDEF&lt;/code&gt;), segment definitions (&lt;code&gt;SEGDEF&lt;/code&gt;), data records (&lt;code&gt;LEDATA&lt;/code&gt;/&lt;code&gt;LIDATA&lt;/code&gt;),
fixups (&lt;code&gt;FIXUPP&lt;/code&gt;), and module end (&lt;code&gt;MODEND&lt;/code&gt;). You do not need to memorize
every byte code to gain value. What matters is recognizing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what this module exports (look for &lt;code&gt;PUBDEF&lt;/code&gt; and similar)&lt;/li&gt;
&lt;li&gt;what this module imports (look for &lt;code&gt;EXTDEF&lt;/code&gt; and unresolved refs)&lt;/li&gt;
&lt;li&gt;where relocation/fixup pressure appears (segments, frame numbers)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example: if &lt;code&gt;tdump FASTBLIT.OBJ&lt;/code&gt; shows a public &lt;code&gt;FastCopy&lt;/code&gt; in segment &lt;code&gt;CODE&lt;/code&gt;,
and your Pascal declares &lt;code&gt;procedure FastBlit(...) external;&lt;/code&gt;, the name mismatch
(&lt;code&gt;FastCopy&lt;/code&gt; vs &lt;code&gt;FastBlit&lt;/code&gt;) will cause &amp;ldquo;unresolved external.&amp;rdquo; The dump gives
you the ground truth. OMF does not standardize symbol decoration; Borland
tools typically emit undecorated public names for Pascal-callable routines,
whereas C compilers may prefix with underscore or use name mangling. If an OBJ
came from a C build, &lt;code&gt;strings&lt;/code&gt; on the OBJ or TDUMP&amp;rsquo;s public list shows the
actual external name—use that exact form in your &lt;code&gt;external&lt;/code&gt; declaration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sample TDUMP output interpretation.&lt;/strong&gt; A typical OBJ dump might show:&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;/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;Module: FASTBLIT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Segment: CODE  Align: Word  Combine: Public
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Publics: FastCopy
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Externals: (none)&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;This tells you: the routine is named &lt;code&gt;FastCopy&lt;/code&gt;, lives in &lt;code&gt;CODE&lt;/code&gt;, and does not
import any external symbols. If your Pascal expects &lt;code&gt;FastBlit&lt;/code&gt; or a different
segment, the mismatch is clear. For LIB dumps, you see one such block per
contained OBJ; scan for the symbol you need and note which module provides it.
If an OBJ lists externals, those must be satisfied by other linked modules or
libraries; unresolved externals at link time usually mean a missing OBJ or LIB
in the link command, or a symbol name typo in the providing module. For LIB
files, link order can matter: the linker pulls in members to satisfy unresolved
externals in sequence. If two OBJs in a LIB have circular references, their
relative order in the archive may determine whether resolution succeeds. When
adding new OBJs to a LIB, run &lt;code&gt;tdump LIBNAME.LIB&lt;/code&gt; afterward to confirm the
member list and publics; TDUMP typically does not reorder members, but some
library tools do. That is enough to explain most &amp;ldquo;why does this link differently
now?&amp;rdquo; questions.&lt;/p&gt;
&lt;h2 id=&#34;map-files-the-fastest-way-to-end-speculation&#34;&gt;Map files: the fastest way to end speculation&lt;/h2&gt;
&lt;p&gt;Generate a map file for non-trivial builds. In IDE: Options → Linker → Map file
(create detailed map). On CLI: &lt;code&gt;TLINK&lt;/code&gt; typically has a &lt;code&gt;/M&lt;/code&gt; or similar switch
for map output. Once you have a map, you can answer quickly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;did the symbol land in the expected segment?&lt;/li&gt;
&lt;li&gt;did the expected object module get linked at all?&lt;/li&gt;
&lt;li&gt;which module caused unexpected size growth?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MAP forensics loop:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build with map enabled. Save &lt;code&gt;GOOD.MAP&lt;/code&gt; as baseline.&lt;/li&gt;
&lt;li&gt;After a change or failure, build again and compare segment/symbol layout.&lt;/li&gt;
&lt;li&gt;If a symbol is missing or moved unexpectedly, trace back to OBJ/TPU
ownership.&lt;/li&gt;
&lt;li&gt;If total size jumps, scan the map for newly included modules or segments.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example interpretation:&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;/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;0001:03A0  MainLoop
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;0001:07C0  DrawHud
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;0002:0010  FastCopy   (from FASTBLIT.OBJ)&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;This gives direct evidence that your assembly object is linked and reachable.
The &lt;code&gt;0002:0010&lt;/code&gt; format is segment:offset; the &lt;code&gt;(from FASTBLIT.OBJ)&lt;/code&gt; annotation
confirms the symbol&amp;rsquo;s origin. If &lt;code&gt;FastCopy&lt;/code&gt; does not appear, the OBJ was not
linked—check &lt;code&gt;{$L}&lt;/code&gt; and link order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;End-to-end artifact workflow example.&lt;/strong&gt; Suppose a project fails to link with
&amp;ldquo;Unresolved external FastBlit.&amp;rdquo;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;tdump ASM\FASTBLIT.OBJ&lt;/code&gt; → inspect publics. If the symbol is &lt;code&gt;FastCopy&lt;/code&gt;
not &lt;code&gt;FastBlit&lt;/code&gt;, fix the Pascal &lt;code&gt;external&lt;/code&gt; declaration to match.&lt;/li&gt;
&lt;li&gt;Verify &lt;code&gt;{$L ASM\FASTBLIT.OBJ}&lt;/code&gt; is present and path correct.&lt;/li&gt;
&lt;li&gt;Rebuild with map enabled. Check that &lt;code&gt;FastCopy&lt;/code&gt; (or corrected name) appears
in the MAP with &lt;code&gt;(from FASTBLIT.OBJ)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If MAP shows the symbol but runtime crashes on call, switch to
calling-convention checklist (near/far, Pascal vs cdecl, parameter order).&lt;/li&gt;
&lt;li&gt;If all above pass, run &lt;code&gt;tdump MYAPP.EXE&lt;/code&gt; and confirm segment layout matches
expectations; then consider disassembly only as a last step.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This sequence uses TPU/OBJ/LIB/MAP/EXE in order of diagnostic payoff. Skipping
to EXE or disassembly before resolving OBJ/MAP questions wastes time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When MAP generation fails.&lt;/strong&gt; Some minimal IDE profiles omit map output by
default. If you cannot enable it, capture at least: EXE file size, list of
&lt;code&gt;{$L}&lt;/code&gt; and &lt;code&gt;uses&lt;/code&gt; entries, and a TDUMP of the EXE for segment layout. That
still beats debugging without any artifact visibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Checksum vs size.&lt;/strong&gt; File size is a fast sanity check; if the EXE grows by
50KB with no new features, something changed. A simple checksum (e.g. DOS
&lt;code&gt;certutil&lt;/code&gt; or Unix &lt;code&gt;cksum&lt;/code&gt;) catches content drift when size alone is
unchanged. For release verification, checksum the EXE and key TPUs/OBJs and
record them in the build log. Teams that automate this in their build script
catch integration drift before it reaches users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MAP format nuances.&lt;/strong&gt; TLINK map files use segment:offset notation; the segment
number corresponds to the link order of segments. A &amp;ldquo;detailed&amp;rdquo; map includes
module origins—which OBJ or unit contributed each segment—so you can trace
size bloat to a specific module. Segment class names (&lt;code&gt;CODE&lt;/code&gt;, &lt;code&gt;DATA&lt;/code&gt;, &lt;code&gt;CSEG&lt;/code&gt;,
&lt;code&gt;DSEG&lt;/code&gt;) reflect compiler/linker output; minor differences across TP versions are
common. When diffing MAPs, compare symbol-to-segment assignments and segment
sizes rather than raw class names. A symbol that moved from one segment to
another between builds can indicate model changes (e.g. near vs far) or link
order tweaks.&lt;/p&gt;
&lt;h2 id=&#34;manipulating-artifacts-safely&#34;&gt;Manipulating artifacts safely&lt;/h2&gt;
&lt;p&gt;Three levels of &amp;ldquo;manipulation&amp;rdquo; exist; do not mix them casually.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Clean rebuild manipulation&lt;/strong&gt;: remove stale TPUs/OBJs and rebuild. Safe and
repeatable. Script it: &lt;code&gt;del *.TPU *.OBJ&lt;/code&gt; (or equivalent) before build.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Link graph manipulation&lt;/strong&gt;: reorder/add/remove OBJ/LIB participation.
Changes code layout; verify with MAP. Can expose far/near or segment
ordering issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Binary patch manipulation&lt;/strong&gt;: edit executable bytes post-link. Risky.
Use only for experiments; document offsets, hashes, and rationale. Never
treat patched binaries as release artifacts without explicit process.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Rule: if a problem appears after link-graph or binary manipulation, revert to
last known-good clean build before drawing conclusions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean script pattern.&lt;/strong&gt; A minimal DOS-era clean step:&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;/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;k&#34;&gt;del&lt;/span&gt; *.TPU *.OBJ &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;nul
&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;exist&lt;/span&gt; BIN\*.EXE &lt;span class=&#34;k&#34;&gt;del&lt;/span&gt; BIN\*.EXE
&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;exist&lt;/span&gt; BIN\*.MAP &lt;span class=&#34;k&#34;&gt;del&lt;/span&gt; BIN\*.MAP&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;Run this before any &amp;ldquo;full rebuild&amp;rdquo; or when chasing artifact-related bugs. Keep
source (&lt;code&gt;.PAS&lt;/code&gt;, &lt;code&gt;.ASM&lt;/code&gt;) and build scripts; treat everything else as regenerable.&lt;/p&gt;
&lt;h2 id=&#34;unit-libraries-and-tpumover-note&#34;&gt;Unit libraries and TPUMOVER note&lt;/h2&gt;
&lt;p&gt;Some TP/BP installations include tooling such as &lt;code&gt;TPUMOVER&lt;/code&gt; for packaging unit
modules into library containers. Availability and exact workflows are
installation-dependent. If present, treat library generation as a release
artifact with version pinning, not as a casual local convenience. Migrating
TPUs between library and loose-file form can alter search order; document
which layout the project uses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Libraries vs loose TPUs.&lt;/strong&gt; Loose TPUs in a directory are easier to
individually inspect, checksum, and replace during development. Library
(TUM-style) packaging reduces file count and can speed unit search on slow
media. Choose one approach per project and stick with it; mixing both for the
same units invites &amp;ldquo;which version did we actually link?&amp;rdquo; confusion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TPUMOVER and library maintenance.&lt;/strong&gt; When you add or remove units from a
library, always rebuild the library from a clean state rather than
incrementally patching. Stale or partially updated libraries produce the same
mystery failures as stale TPUs. After any library change, run a full clean
rebuild of the main program and verify the MAP reflects the expected unit set.
Treat the library as an intermediate build product, not a hand-edited asset.&lt;/p&gt;
&lt;h2 id=&#34;external-obj-integration-robust-declaration-pattern&#34;&gt;External OBJ integration: robust declaration pattern&lt;/h2&gt;
&lt;p&gt;Pascal side:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{$L FASTBLIT.OBJ}
procedure FastBlit(var Dst; const Src; Count: Word); external;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Expected outcome before first run:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;link succeeds with no unresolved external&lt;/li&gt;
&lt;li&gt;call does not corrupt stack&lt;/li&gt;
&lt;li&gt;output buffer changes exactly as test vector predicts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If link succeeds but behavior is wrong, suspect ABI mismatch first. Before
blaming the algorithm, verify parameter alignment: Turbo Pascal typically aligns
parameters to word boundaries; an assembly routine expecting byte-precise
layout may read garbage. Return-value handling also varies: functions returning
&lt;code&gt;Word&lt;/code&gt; or &lt;code&gt;Integer&lt;/code&gt; use AX; &lt;code&gt;LongInt&lt;/code&gt; uses DX:AX; records and strings use
hidden pointer parameters. Document what your external returns and how the
caller expects it; mismatches cause wrong values, not link errors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Calling-convention cautions.&lt;/strong&gt; Turbo Pascal&amp;rsquo;s default calling convention
(typically near, Pascal-style: left-to-right push, caller cleans stack) must
match the external routine. Common failure modes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;C vs Pascal convention&lt;/strong&gt;: C pushes right-to-left and often uses different
name decoration. If the OBJ came from C (&lt;code&gt;TCC&lt;/code&gt;, &lt;code&gt;BCC&lt;/code&gt;), declare with
&lt;code&gt;cdecl&lt;/code&gt; or equivalent where the compiler supports it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Near vs far&lt;/strong&gt;: &lt;code&gt;{$F+}&lt;/code&gt; forces far calls; assembly routines must use
&lt;code&gt;RET FAR&lt;/code&gt; and matching prolog. Mismatch causes return to wrong address.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameter order and types&lt;/strong&gt;: &lt;code&gt;var&lt;/code&gt; passes pointer; &lt;code&gt;const&lt;/code&gt; can pass
pointer or value depending on size. Word-sized &lt;code&gt;Count&lt;/code&gt; must match assembly
expectations (byte, word, or dword).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Segment assumptions&lt;/strong&gt;: If the OBJ assumes a particular &lt;code&gt;DS&lt;/code&gt; or &lt;code&gt;ES&lt;/code&gt; setup,
document it. Pascal does not guarantee segment registers at call boundary.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Document every external in a small header comment: source file, compiler/TASM
options used, calling convention, and any non-default assumptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration test pattern.&lt;/strong&gt; Before relying on an external in production
code, add a minimal harness that calls it with known inputs and verifies
output. For example, fill two buffers, call the routine, and assert the
result. If that passes, the OBJ is correctly integrated; failures point to
convention or parameter mismatches before you bury the call in complex logic.
Run it immediately after linking.&lt;/p&gt;
&lt;p&gt;TP5 reference also states &lt;code&gt;{$L filename}&lt;/code&gt; is a local directive and searches
object directories when a path is not explicit, which is a common source of
machine-to-machine drift. Prefer explicit paths in build scripts: &lt;code&gt;{$L ASM\FASTBLIT.OBJ}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLIB workflow for multi-module assembly.&lt;/strong&gt; When you have several &lt;code&gt;.ASM&lt;/code&gt; files
producing &lt;code&gt;.OBJ&lt;/code&gt; modules, you can either list each with &lt;code&gt;{$L mod1.OBJ}&lt;/code&gt; &lt;code&gt;{$L mod2.OBJ}&lt;/code&gt; &amp;hellip; or build a &lt;code&gt;.LIB&lt;/code&gt; and link that. TLIB creates/updates
libraries:&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;/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;tlib FASTMATH +FASTBLIT +FASTMUL +FASTDIV&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;Then &lt;code&gt;{$L FASTMATH.LIB}&lt;/code&gt; pulls in all modules. TDUMP on the LIB shows which
modules and publics it contains. Use a LIB when you have many OBJ files and
want a single linkable unit; keep OBJ references when you need explicit control
over link order (e.g. for overlays or segment placement).&lt;/p&gt;
&lt;h2 id=&#34;exe-level-checks-before-disassembly&#34;&gt;EXE-level checks before disassembly&lt;/h2&gt;
&lt;p&gt;Before deep reversing, inspect executable-level metadata. TDUMP on &lt;code&gt;.EXE&lt;/code&gt; shows
DOS header, relocation table, segment layout, and entry point. The DOS header
contains the relocation count (number of fixups applied at load), initial CS:IP
(entry point), and initial SS:SP (stack). Relocation entries point to segment
references that the loader patches when loading at a non-default base; a change
in relocation count often indicates new far pointers or segment-relative refs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;High-signal EXE checks:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;relocation count changes (indicates new segments or far model shifts)&lt;/li&gt;
&lt;li&gt;stack/code entry metadata drift&lt;/li&gt;
&lt;li&gt;total image size deltas&lt;/li&gt;
&lt;li&gt;segment order and class names (e.g. &lt;code&gt;CODE&lt;/code&gt;, &lt;code&gt;DATA&lt;/code&gt;, &lt;code&gt;STACK&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&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;/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;tdump MYAPP.EXE &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; findstr /i &lt;span class=&#34;s2&#34;&gt;&amp;#34;reloc entry segment&amp;#34;&lt;/span&gt;&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;Or capture full dump and diff against known-good:&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;/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;tdump MYAPP.EXE &lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; MYAPP_EXE.DMP
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;fc /b MYAPP_EXE.DMP BASELINE_EXE.DMP&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;Large unexpected changes usually indicate build-profile or link-graph drift,
not random compiler mood. This quick check avoids hours of aimless debugging.
If the EXE header and relocation table match a known-good build, but behavior
differs, the problem is likely runtime (paths, overlays, memory) rather than
link-time.&lt;/p&gt;
&lt;h2 id=&#34;high-value-troubleshooting-table&#34;&gt;High-value troubleshooting table&lt;/h2&gt;
&lt;p&gt;Use this as a repeatable decision matrix. Check in order; do not skip to
disassembly before ruling out high-signal causes. The goal is to eliminate
most failures with minimal tool use—TDUMP, MAP diff, and clean rebuild cover
the majority of cases.&lt;/p&gt;
&lt;h3 id=&#34;unresolved-external&#34;&gt;&amp;ldquo;Unresolved external&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Most likely causes (check first):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;symbol spelling/case mismatch (TDUMP the OBJ for exact public name)&lt;/li&gt;
&lt;li&gt;missing object or library in link graph (verify &lt;code&gt;{$L}&lt;/code&gt; and TLINK command)&lt;/li&gt;
&lt;li&gt;module compiled for incompatible object format/profile (OMF vs COFF, etc.)&lt;/li&gt;
&lt;li&gt;wrong unit or OBJ pulled from alternate path (path order, current dir)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; &lt;code&gt;tdump SYMBOL.OBJ | findstr /i &amp;quot;public pubdef&amp;quot;&lt;/code&gt; — does the
exported name match your Pascal &lt;code&gt;external&lt;/code&gt; declaration exactly?&lt;/p&gt;
&lt;h3 id=&#34;runs-then-random-crash-after-external-call&#34;&gt;&amp;ldquo;Runs, then random crash after external call&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Most likely causes (check first):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;parameter passing mismatch (order, size, var vs value)&lt;/li&gt;
&lt;li&gt;caller/callee stack cleanup mismatch (Pascal vs cdecl)&lt;/li&gt;
&lt;li&gt;near/far routine mismatch (return address on wrong stack location)&lt;/li&gt;
&lt;li&gt;segment register assumptions violated (DS, ES not as assembly expects)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; Add a minimal passthrough test: call the routine with
known-good inputs and confirm output. If that works, the failure is in
integration, not the routine itself.&lt;/p&gt;
&lt;h3 id=&#34;unit-version-mismatch&#34;&gt;&amp;ldquo;Unit version mismatch&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Most likely causes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;TPU built by different compiler version&lt;/li&gt;
&lt;li&gt;interface changed but dependent unit not recompiled&lt;/li&gt;
&lt;li&gt;stale TPU in a path that shadows the correct one&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; Delete all TPUs, rebuild from scratch. If it works, you had
stale artifacts.&lt;/p&gt;
&lt;h3 id=&#34;binary-suddenly-huge&#34;&gt;&amp;ldquo;Binary suddenly huge&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Most likely causes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;profile drift (debug info/checks enabled)&lt;/li&gt;
&lt;li&gt;broad library dependency pull&lt;/li&gt;
&lt;li&gt;accidental static inclusion of assets/modules (BGI linked in, large data)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; Compare MAP files. New segments or modules explain the growth.&lt;/p&gt;
&lt;h3 id=&#34;works-on-my-machine-fails-elsewhere&#34;&gt;&amp;ldquo;Works on my machine, fails elsewhere&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Most likely causes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;path differences (unit dir, object dir, BGI dir, overlay dir)&lt;/li&gt;
&lt;li&gt;different DOS/TSR footprint (less conventional memory)&lt;/li&gt;
&lt;li&gt;different compiler or RTL version installed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; Document paths and versions on working machine; replicate
exactly on failing one, or ship with explicit relative paths.&lt;/p&gt;
&lt;h3 id=&#34;overlay-load-fails-or-hangs&#34;&gt;&amp;ldquo;Overlay load fails or hangs&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Most likely causes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;OVR file not in working directory or configured overlay path&lt;/li&gt;
&lt;li&gt;overlay unit compiled with different memory model than main program&lt;/li&gt;
&lt;li&gt;overlay segment size exceeds OVR file (truncated or mismatched build)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Quick check:&lt;/strong&gt; Confirm OVR file size matches expectations; run &lt;code&gt;tdump&lt;/code&gt; on the
EXE to see overlay segment declarations. Compare with a known-good overlay build.&lt;/p&gt;
&lt;h3 id=&#34;summary-signal-order-for-artifact-inspection&#34;&gt;Summary: signal order for artifact inspection&lt;/h3&gt;
&lt;p&gt;When you do not know where to start, use this priority:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;MAP&lt;/strong&gt; — fastest way to see what actually linked. Generate it; diff it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OBJ/LIB + TDUMP&lt;/strong&gt; — resolves &amp;ldquo;unresolved external&amp;rdquo; and symbol-name issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TPU&lt;/strong&gt; — resolves &amp;ldquo;unit version mismatch&amp;rdquo; and interface drift; use
differential forensics when format is unknown.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EXE + TDUMP&lt;/strong&gt; — confirms final layout; use when MAP and OBJ checks pass
but runtime behavior is wrong.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disassembly&lt;/strong&gt; — last resort when binary layout is correct but logic is
suspect.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most TP toolchain bugs are solved at steps 1–3. Avoid jumping to 4–5 without
evidence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Checkpoint discipline.&lt;/strong&gt; When you have a working build, immediately: (a) save
&lt;code&gt;BASELINE.MAP&lt;/code&gt;, (b) note EXE size and optionally CRC, (c) archive BUILD.TXT.
If a later change breaks things, you can diff MAP vs baseline, compare sizes,
and often pinpoint the regression without touching source. Teams that skip
checkpoints repeat the same forensic work repeatedly. A single baseline from a
known-good build can save hours of regression hunting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before seeking help.&lt;/strong&gt; If you are stuck and plan to ask a colleague or
post online, gather: exact error message, compiler/linker version, output of
&lt;code&gt;tdump&lt;/code&gt; on the failing OBJ (for link errors) or EXE (for runtime), and a
one-line description of the last change. That context turns &amp;ldquo;it doesn&amp;rsquo;t work&amp;rdquo;
into a solvable puzzle. Omitting the MAP or TDUMP output is the most common
reason diagnostic threads go nowhere.&lt;/p&gt;
&lt;h2 id=&#34;a-disciplined-binary-investigation-loop&#34;&gt;A disciplined binary investigation loop&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;state expected outcome before run&lt;/li&gt;
&lt;li&gt;build clean (no stale TPU/OBJ)&lt;/li&gt;
&lt;li&gt;capture &lt;code&gt;.EXE&lt;/code&gt; size/hash + &lt;code&gt;.MAP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;inspect changed symbols/segments first&lt;/li&gt;
&lt;li&gt;only then debug/disassemble&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This order keeps you from chasing folklore. Teams that skip step 3 often waste
hours on &amp;ldquo;it used to work&amp;rdquo; bugs that are pure link/artifact drift.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When the loop stalls.&lt;/strong&gt; If you have done clean rebuild, MAP diff, TDUMP on
OBJ and EXE, and the problem persists, the cause may be environmental: TSR
conflicts, EMS/XMS driver behavior, or DOS version differences. At that point
narrow the environment: boot minimal config, disable TSRs, try a different DOS
version or machine. Document the minimal repro configuration; that becomes the
bug report. Before concluding &amp;ldquo;environment only,&amp;rdquo; re-run the loop with a
single-source-change variation: revert the most recent edit, rebuild, and
compare. If the revert fixes it, the regression is in that change, not the
environment—even when the artifact diff is subtle.&lt;/p&gt;
&lt;h2 id=&#34;team-and-process-discipline-for-artifact-reproducibility&#34;&gt;Team and process discipline for artifact reproducibility&lt;/h2&gt;
&lt;p&gt;Reproducibility fails when one developer has hidden state that others do not.
Enforce these practices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Version-lock the toolchain&lt;/strong&gt;: document exact TP/BP version, TASM version,
and any third-party units. Rebuild from source on a clean checkout must
produce identical artifacts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit paths in scripts&lt;/strong&gt;: avoid &amp;ldquo;current directory&amp;rdquo; assumptions. Build
scripts should set &lt;code&gt;PATH&lt;/code&gt;, unit dirs, and object dirs explicitly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archive build products with releases&lt;/strong&gt;: keep &lt;code&gt;EXE&lt;/code&gt; + &lt;code&gt;MAP&lt;/code&gt; + optional
&lt;code&gt;OVR&lt;/code&gt; and a short &lt;code&gt;BUILD.TXT&lt;/code&gt; (compiler version, options, date) in the
release package. That gives future maintainers a diff target.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One clean rebuild before any &amp;ldquo;weird bug&amp;rdquo; investigation&lt;/strong&gt;: if a bug appears
after days of incremental builds, delete TPUs/OBJs and rebuild. Many
&amp;ldquo;impossible&amp;rdquo; bugs vanish.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ABI checkpoint for externals&lt;/strong&gt;: when integrating a new OBJ, record its
public symbols (from TDUMP), calling convention, and any segment or
alignment assumptions in a small integration doc. Future maintainers can
verify correctness without re-deriving the ABI from scratch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treat TPU/OBJ as derived, never committed&lt;/strong&gt;: only source (&lt;code&gt;.PAS&lt;/code&gt;, &lt;code&gt;.ASM&lt;/code&gt;)
goes in version control. Rebuild artifact sets from source on each machine.
Committed TPUs from one developer&amp;rsquo;s machine can silently break another&amp;rsquo;s
build when compiler versions differ. Document this policy in the project
README.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These rules are low-cost and eliminate a large class of non-reproducible
failures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build log discipline.&lt;/strong&gt; For each release or debugging baseline, record in
&lt;code&gt;BUILD.TXT&lt;/code&gt; or equivalent: compiler executable and version, key options
(&lt;code&gt;{$D+}&lt;/code&gt;, &lt;code&gt;{$R+}&lt;/code&gt;, memory model), unit and object paths, and checksum or size
of the main EXE. When a bug report arrives months later, that log tells you
whether you can reproduce the exact binary or must narrow the search.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Handoff protocol.&lt;/strong&gt; When passing a project to another maintainer, include:
source tree, BUILD.BAT or equivalent, BASELINE.MAP from last known-good build,
and a one-page &amp;ldquo;toolchain and paths&amp;rdquo; document. Without that, the next person
spends days rediscovering unit search order, object paths, and which TP
version was used. The hour you spend documenting pays off on the first
&amp;ldquo;works on my machine&amp;rdquo; incident.&lt;/p&gt;
&lt;h2 id=&#34;cross-references&#34;&gt;Cross references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-1-anatomy-and-workflow/&#34;&gt;Turbo Pascal Toolchain, Part 1: Anatomy and Workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-units-as-architecture/&#34;&gt;Turbo Pascal Units as Architecture, Not Just Reuse&lt;/a&gt;&lt;/li&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/turbo-pascal-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/&#34;&gt;Turbo Pascal BGI Tutorial: Dynamic Drivers, Linked Drivers, and Diagnostic Harnesses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-in-2025/&#34;&gt;Writing Turbo Pascal in 2025&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;next-part&#34;&gt;Next part&lt;/h2&gt;
&lt;p&gt;Part 3 moves from artifacts to runtime memory strategy: overlays, near/far
costs, and link strategy under hard 640K pressure.&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;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Summary for busy maintainers.&lt;/strong&gt; When a TP project misbehaves: (1) clean
rebuild first; (2) generate and diff the MAP; (3) TDUMP any external OBJs to
confirm symbol names; (4) verify calling conventions on externals; (5) check
path and version consistency. Most failures resolve before you touch a
disassembler. Treat TPU/OBJ as version-locked, path-explicit, and
never-committed. Document once; benefit forever. The artifact-focused mindset
that Part 1 introduced becomes concrete here: files on disk are your primary
evidence, source code is secondary when debugging build and link failures.&lt;/p&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>
    
    <item>
      <title>Turbo Pascal Toolchain, Part 4: Graphics Drivers, BGI, and Rendering Integration</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-4-graphics-drivers-bgi-and-rendering-integration/</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-4-graphics-drivers-bgi-and-rendering-integration/</guid>
      <description>&lt;p&gt;Turbo Pascal graphics was never just &amp;ldquo;call &lt;code&gt;Graph&lt;/code&gt; and draw.&amp;rdquo; In production-ish
DOS projects, graphics was an asset pipeline problem, a deployment problem, and
a diagnostics problem at least as much as an API problem.&lt;/p&gt;
&lt;p&gt;This part focuses on BGI driver mechanics, practical packaging, and the exact
checks that separate real faults from folklore.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Structure map:&lt;/strong&gt; BGI architecture and operational models → Graph unit runtime
contracts and &lt;code&gt;GraphResult&lt;/code&gt; handling → dynamic vs linked drivers, packaging and
pitfalls → font/driver matrix and memory interactions → BGI artifacts in build
and deploy pipelines → debugging rendering failures on real DOS → team checklists
and release hardening.&lt;/p&gt;
&lt;h2 id=&#34;bgi-architecture-in-practical-terms&#34;&gt;BGI architecture in practical terms&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Graph&lt;/code&gt; unit provides the API. Under it, runtime driver/font assets do the
hardware-specific work. The unit itself is statically linked; it does not
contain adapter-specific code. Instead, it loads a driver binary that knows
how to program the hardware (CGA, EGA, VGA, Hercules, etc.) and interpret
high-level drawing calls. Fonts are separate assets because they are large
and optional — you only load the ones your UI needs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;driver assets: usually &lt;code&gt;.BGI&lt;/code&gt; (e.g. &lt;code&gt;EGAVGA.BGI&lt;/code&gt;, &lt;code&gt;CGA.BGI&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;font assets: &lt;code&gt;.CHR&lt;/code&gt; stroked fonts (e.g. &lt;code&gt;TRIP.CHR&lt;/code&gt;, &lt;code&gt;GOTH.CHR&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;initialization: &lt;code&gt;InitGraph(driver, mode, path)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;status reporting: &lt;code&gt;GraphResult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;cleanup: &lt;code&gt;CloseGraph&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two operational models exist:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Dynamic&lt;/strong&gt; runtime loading from filesystem path — driver and font files are
read from disk at &lt;code&gt;InitGraph&lt;/code&gt; time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linked-driver&lt;/strong&gt; — driver (and optionally font) binaries converted to &lt;code&gt;.OBJ&lt;/code&gt;
and linked into the executable; registration APIs make them available before
&lt;code&gt;InitGraph&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both are valid. Pick by deployment constraints: dynamic keeps builds small and
simple but requires correct runtime paths; linked reduces file dependencies and
installer mistakes at the cost of executable size and build coupling. Many
teams shipped dynamic for development and internal testing, then produced a
linked-driver build for floppy or constrained deployments where users were
unlikely to preserve directory structure correctly.&lt;/p&gt;
&lt;h2 id=&#34;graph-unit-runtime-contracts-and-graphresult-handling&#34;&gt;Graph unit runtime contracts and GraphResult handling&lt;/h2&gt;
&lt;p&gt;Every Graph operation that can fail updates an internal error code. &lt;code&gt;GraphResult&lt;/code&gt;
returns that code and, in TP5, &lt;strong&gt;resets it to zero on read&lt;/strong&gt;. That one-read
semantic causes subtle bugs when code checks &lt;code&gt;GraphResult&lt;/code&gt; multiple times or
assumes it remains set across calls.&lt;/p&gt;
&lt;p&gt;Contract rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;GraphResult&lt;/code&gt; once after any operation that may fail, store the value in
a local variable, then branch on that variable.&lt;/li&gt;
&lt;li&gt;Do not assume &lt;code&gt;GraphResult&lt;/code&gt; stays non-zero until the next failed operation.&lt;/li&gt;
&lt;li&gt;Never call &lt;code&gt;GraphResult&lt;/code&gt; before the operation you intend to check — earlier
successful operations clear it.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ WRONG: second check sees zero from first read }
InitGraph(gd, gm, &amp;#39;.\BGI&amp;#39;);
if GraphResult &amp;lt;&amp;gt; grOk then Halt(1);
if GraphResult &amp;lt;&amp;gt; grOk then ...   { always passes; result was cleared }

{ RIGHT: single read, then use stored value }
InitGraph(gd, gm, &amp;#39;.\BGI&amp;#39;);
gr := GraphResult;
if gr &amp;lt;&amp;gt; grOk then
  begin
    Writeln(&amp;#39;Init failed: &amp;#39;, gr);
    Halt(1);
  end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;TP5 error codes worth memorizing for triage:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Code&lt;/th&gt;
          &lt;th&gt;Constant&lt;/th&gt;
          &lt;th&gt;Typical cause&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grOk&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Success&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-1&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grNoInitGraph&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Graphics not initialized&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-2&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grNotDetected&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;No compatible adapter found&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-3&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grFileNotFound&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Driver or font file missing on path&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-4&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grInvalidDriver&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Driver format invalid or mismatched&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-5&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grNoLoadMem&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Not enough heap for driver/buffer&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-8&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grFontNotFound&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Font file missing&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-9&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grNoFontMem&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Not enough heap for font&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-10&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grInvalidMode&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Mode not supported by driver&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;-11&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;grError&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;General error; often registration/order violation&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;When &lt;code&gt;grNoLoadMem&lt;/code&gt; appears, suspect overlay buffer sizing or TSR load order
before blaming hardware. When &lt;code&gt;grFileNotFound&lt;/code&gt; appears, verify &lt;code&gt;PathToDriver&lt;/code&gt;
resolves from the process&amp;rsquo;s current directory, not the source tree. Some TP/BP
variants may use &lt;code&gt;PathStr&lt;/code&gt; or environment variables for default paths; the
TP5 reference is explicit that an empty path means current directory, and
documentation for later versions should be consulted if behavior differs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Uncertainty note:&lt;/strong&gt; Exact &lt;code&gt;GraphResult&lt;/code&gt; semantics and numeric codes can
vary slightly between TP5, TP6, TP7, and BP7. The table above reflects TP5
reference values; when targeting multiple versions, confirm codes in your
toolchain&amp;rsquo;s &lt;code&gt;GRAPH.TPU&lt;/code&gt; or include files.&lt;/p&gt;
&lt;h2 id=&#34;tp5-baseline-facts-from-the-reference-guide&#34;&gt;TP5 baseline facts from the reference guide&lt;/h2&gt;
&lt;p&gt;For Turbo Pascal 5.0, the reference guide is explicit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;compile-time dependency: &lt;code&gt;GRAPH.TPU&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;runtime dependency: one or more &lt;code&gt;.BGI&lt;/code&gt; drivers&lt;/li&gt;
&lt;li&gt;if stroked fonts are used: one or more &lt;code&gt;.CHR&lt;/code&gt; files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;InitGraph&lt;/code&gt; loads the selected driver and enters graphics mode. &lt;code&gt;CloseGraph&lt;/code&gt;
unloads/restores previous mode. This is the lifecycle baseline. After
&lt;code&gt;CloseGraph&lt;/code&gt;, you may re-enter graphics mode with another &lt;code&gt;InitGraph&lt;/code&gt; call, but
driver and font state are reset; any registered user drivers must be re-registered
if you use the linked model.&lt;/p&gt;
&lt;h2 id=&#34;dynamic-model-fastest-to-start-easiest-to-break-in-deployment&#34;&gt;Dynamic model: fastest to start, easiest to break in deployment&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;uses Graph;
var
  gd, gm, gr: Integer;
begin
  gd := Detect;
  InitGraph(gd, gm, &amp;#39;C:\APP\BGI&amp;#39;);
  gr := GraphResult;
  if gr &amp;lt;&amp;gt; grOk then
    begin
      Writeln(&amp;#39;BGI init failed: &amp;#39;, gr);
      Halt(1);
    end;
  { render }
  CloseGraph;
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Expected outcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;works immediately in dev environment with full BGI directory&lt;/li&gt;
&lt;li&gt;fails fast if path/assets are missing, with actionable error code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Common failure is not code. It is wrong path assumptions after installation.
Typical mistakes: hardcoding &lt;code&gt;C:\TP\BGI&lt;/code&gt; or &lt;code&gt;.\BGI&lt;/code&gt; when the app runs from
&lt;code&gt;A:\&lt;/code&gt; or a network drive; assuming &lt;code&gt;GetDir&lt;/code&gt; equals executable directory; using
forward slashes on systems that expect backslashes.&lt;/p&gt;
&lt;p&gt;TP5 path behavior: if &lt;code&gt;PathToDriver&lt;/code&gt; is empty, driver files must be in the
current directory. If you pass a path, it must end with a trailing backslash on
some implementations to be treated as a directory. Conservative practice: always
pass an explicit path built from &lt;code&gt;ParamStr(0)&lt;/code&gt; or &lt;code&gt;GetDir&lt;/code&gt;, and ensure it ends
with &lt;code&gt;\&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Path resolution example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;uses Dos, Graph;
var
  ExeDir, BgiPath: PathStr;
  Name, Ext: PathStr;
begin
  FSplit(ParamStr(0), ExeDir, Name, Ext);
  BgiPath := ExeDir + &amp;#39;BGI&amp;#39; + &amp;#39;\&amp;#39;;
  InitGraph(gd, gm, BgiPath);
  gr := GraphResult;
  ...
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This assumes &lt;code&gt;BGI&lt;/code&gt; is a subdirectory next to the executable. If you ship with
&lt;code&gt;BGI&lt;/code&gt; alongside &lt;code&gt;.EXE&lt;/code&gt;, this pattern works regardless of where the user
installed the app.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Triage for dynamic-load failures:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run the diagnostic harness (see below) from the same directory and path the
app will use in production.&lt;/li&gt;
&lt;li&gt;If harness works but app fails, compare paths and current-directory
assumptions between harness and app.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;grFileNotFound&lt;/code&gt;: list directory contents, verify file names match
exactly (case may matter on some setups).&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;grNoLoadMem&lt;/code&gt;: reduce overlay buffer, close TSRs, or switch to linked
driver.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;linked-driver-model-more-robust-runtime-tighter-build-coupling&#34;&gt;Linked-driver model: more robust runtime, tighter build coupling&lt;/h2&gt;
&lt;p&gt;Some Borland-era toolchains support converting/linking driver binaries into
object form and registering them at startup (for example via &lt;code&gt;RegisterBGIdriver&lt;/code&gt;
and companion font registration APIs). This avoids runtime dependency on
external &lt;code&gt;.BGI&lt;/code&gt; files but increases binary size and build complexity.&lt;/p&gt;
&lt;p&gt;Practical pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;convert/select driver object module&lt;/li&gt;
&lt;li&gt;link object into project (&lt;code&gt;{$L ...}&lt;/code&gt; or linker config)&lt;/li&gt;
&lt;li&gt;register driver before &lt;code&gt;InitGraph&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;InitGraph&lt;/code&gt; with empty or local path expectations&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Exact symbol names and conversion utilities depend on installation/profile, so
document your specific toolchain once and keep it version-pinned.&lt;/p&gt;
&lt;p&gt;TP5 manual flow for linked drivers is concrete:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;convert &lt;code&gt;.BGI&lt;/code&gt; to &lt;code&gt;.OBJ&lt;/code&gt; with &lt;code&gt;BINOBJ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;link resulting &lt;code&gt;.OBJ&lt;/code&gt; into the executable&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;RegisterBGIdriver&lt;/code&gt; before &lt;code&gt;InitGraph&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you call &lt;code&gt;RegisterBGIdriver&lt;/code&gt; after graphics are already active, TP5 reports
&lt;code&gt;grError&lt;/code&gt; (&lt;code&gt;-11&lt;/code&gt;). Same rule applies to &lt;code&gt;RegisterBGIfont&lt;/code&gt;: register before
first use of that font.&lt;/p&gt;
&lt;p&gt;BINOBJ invocation (exact syntax varies by Borland install):&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;/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;BINOBJ EGAVGA.BGI EGAVGA EGAVGA&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;This produces &lt;code&gt;EGAVGA.OBJ&lt;/code&gt; with symbols for the binary blob. The linker then
pulls it in via &lt;code&gt;{$L EGAVGA.OBJ}&lt;/code&gt;. The two symbol names after the filename
are typically the public name and the segment/object name; consult your
BINOBJ documentation. After conversion, add the &lt;code&gt;.OBJ&lt;/code&gt; to your build and
ensure it is linked before the unit that calls &lt;code&gt;RegisterBGIdriver&lt;/code&gt;. If the
symbol is undefined at link time, the &lt;code&gt;.OBJ&lt;/code&gt; was not included or the
declaration does not match BINOBJ output.&lt;/p&gt;
&lt;p&gt;Illustrative registration shape (symbol names vary by conversion/tooling):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{$L EGAVGA.OBJ}

procedure RegisterEgaVga; external;

begin
  RegisterBGIdriver(@RegisterEgaVga);
  { or InstallUserDriver + callback, depending on toolchain }
  InitGraph(gd, gm, &amp;#39;&amp;#39;);
  gr := GraphResult;
  if gr &amp;lt;&amp;gt; grOk then Halt(1);
  { ... }
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Treat symbol names as toolchain-specific; BINOBJ output and TP/BP docs define
the exact entry. If registration order is wrong, you get &lt;code&gt;grError&lt;/code&gt; with no
obvious message — add logging before each Graph call during integration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pitfalls:&lt;/strong&gt; Forgetting to register before &lt;code&gt;InitGraph&lt;/code&gt;; registering after
&lt;code&gt;InitGraph&lt;/code&gt;; linking the wrong driver &lt;code&gt;.OBJ&lt;/code&gt; for the target adapter; mixing
driver versions (e.g. TP5 vs BP7) when BGI format differs. Another pitfall:
assuming &lt;code&gt;Detect&lt;/code&gt; returns the same driver on all VGA systems. Some VGA clones
or BIOS quirks can cause &lt;code&gt;Detect&lt;/code&gt; to fail or return a conservative mode;
hardcoding a fallback (e.g. &lt;code&gt;if gd = Detect then gd := VGA; gm := VGAHi&lt;/code&gt;) can
improve robustness when autodetect is unreliable. When linking multiple
drivers (e.g. VGA + CGA fallback), register all before &lt;code&gt;InitGraph&lt;/code&gt;; the order
may matter for some toolchains — consult your Graph unit docs. A linked build
that works in the IDE can fail at standalone run if the &lt;code&gt;.OBJ&lt;/code&gt; was not linked
or the external symbol name does not match BINOBJ output; add a build step
that verifies the linked executable size increased by the expected driver
blob size.&lt;/p&gt;
&lt;h2 id=&#34;asset-set-discipline-driver--font-matrix&#34;&gt;Asset set discipline (driver + font matrix)&lt;/h2&gt;
&lt;p&gt;For each shipping mode profile, define and freeze:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;required driver files (e.g. &lt;code&gt;EGAVGA.BGI&lt;/code&gt; for VGA, &lt;code&gt;CGA.BGI&lt;/code&gt; for CGA fallback)&lt;/li&gt;
&lt;li&gt;required font files (e.g. &lt;code&gt;TRIP.CHR&lt;/code&gt;, &lt;code&gt;GOTH.CHR&lt;/code&gt; if &lt;code&gt;SetTextStyle&lt;/code&gt; uses them)&lt;/li&gt;
&lt;li&gt;fallback mode behavior (what mode to try if Detect fails or preferred mode unavailable)&lt;/li&gt;
&lt;li&gt;startup diagnostics text (what to print on failure)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without this matrix, BGI deployment drifts silently between machines. One
developer ships with &lt;code&gt;EGAVGA.BGI&lt;/code&gt; only; another&amp;rsquo;s machine has &lt;code&gt;CGA.BGI&lt;/code&gt; in path;
field reports &amp;ldquo;black screen&amp;rdquo; and nobody knows which adapter or driver set was
used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Driver and font packaging rules:&lt;/strong&gt; Drivers and fonts must be version-pinned to
the toolchain that produced &lt;code&gt;GRAPH.TPU&lt;/code&gt;. TP5 &lt;code&gt;EGAVGA.BGI&lt;/code&gt; is not guaranteed
compatible with BP7&amp;rsquo;s Graph unit; format and entry-point layout can differ.
Package drivers as a locked set: document &amp;ldquo;EGAVGA.BGI from TP5.0 install dated
1989&amp;rdquo; in your release notes. Fonts are similarly sensitive: a &lt;code&gt;.CHR&lt;/code&gt; from one
toolchain may load but render incorrectly with another. When upgrading the
compiler, re-validate the full driver+font matrix against your harness before
cutting a release. Include file sizes and checksums in the manifest so swapped
or corrupted copies are detectable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Font/driver matrix discipline:&lt;/strong&gt; Not all fonts work with all drivers. Stroked
fonts (&lt;code&gt;.CHR&lt;/code&gt;) are driver-independent in principle, but &lt;code&gt;SetTextStyle&lt;/code&gt; calls
before a font is loaded fall back to default. Document which fonts are required
for each UI path. If you use &lt;code&gt;InstallUserFont&lt;/code&gt; or &lt;code&gt;RegisterBGIfont&lt;/code&gt;, the
registration order and timing must match the matrix — register before any
&lt;code&gt;SetTextStyle&lt;/code&gt; that selects that font. A minimal matrix might look like:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Target adapter&lt;/th&gt;
          &lt;th&gt;Driver&lt;/th&gt;
          &lt;th&gt;Fonts used&lt;/th&gt;
          &lt;th&gt;Fallback mode&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;VGA&lt;/td&gt;
          &lt;td&gt;EGAVGA.BGI&lt;/td&gt;
          &lt;td&gt;TRIP, GOTH&lt;/td&gt;
          &lt;td&gt;VGAHi&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;EGA&lt;/td&gt;
          &lt;td&gt;EGAVGA.BGI&lt;/td&gt;
          &lt;td&gt;TRIP&lt;/td&gt;
          &lt;td&gt;EGALo&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;CGA&lt;/td&gt;
          &lt;td&gt;CGA.BGI&lt;/td&gt;
          &lt;td&gt;(default)&lt;/td&gt;
          &lt;td&gt;CGAC0&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ship only drivers and fonts listed for your supported targets. Including extra
files &amp;ldquo;just in case&amp;rdquo; increases install size and the chance of path confusion.
Update the matrix when adding support for new adapters (e.g. Hercules, MCGA)
or when dropping support for legacy hardware.&lt;/p&gt;
&lt;h2 id=&#34;bgi-artifacts-in-build-and-deploy-pipelines&#34;&gt;BGI artifacts in build and deploy pipelines&lt;/h2&gt;
&lt;p&gt;BGI assets are build outputs as much as runtime dependencies. Include them in
your artifact pipeline so releases are reproducible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Package layout:&lt;/strong&gt;&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;/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;RELEASE/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  MYAPP.EXE
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  BGI/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    EGAVGA.BGI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    CGA.BGI
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  FONTS/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    TRIP.CHR
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    GOTH.CHR
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  README.TXT&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;If using linked drivers, &lt;code&gt;BGI/&lt;/code&gt; and &lt;code&gt;FONTS/&lt;/code&gt; may be empty, but the layout
should still be documented so installers know what to expect.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build script integration:&lt;/strong&gt;&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;/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;k&#34;&gt;set&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BGI_SRC&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;C:\TP\BGI
&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;set&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;BGI_OUT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;..\RELEASE\BGI
&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; &lt;span class=&#34;nv&#34;&gt;%BGI_OUT%&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;mkdir&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;%BGI_OUT%&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;copy&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;%BGI_SRC%&lt;/span&gt;\EGAVGA.BGI &lt;span class=&#34;nv&#34;&gt;%BGI_OUT%&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;copy&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;%BGI_SRC%&lt;/span&gt;\CGA.BGI &lt;span class=&#34;nv&#34;&gt;%BGI_OUT%&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; ..\RELEASE\FONTS &lt;span class=&#34;k&#34;&gt;mkdir&lt;/span&gt; ..\RELEASE\FONTS
&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;copy&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;%BGI_SRC%&lt;/span&gt;\TRIP.CHR ..\RELEASE\FONTS\
&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 checksum for release manifest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;... checksum tool ...&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;Run the diagnostic harness as a post-build step: execute it from &lt;code&gt;RELEASE\&lt;/code&gt;
with &lt;code&gt;BGI&lt;/code&gt; as subdirectory and assert &lt;code&gt;GraphResult = grOk&lt;/code&gt;. If the harness
fails in clean build output, fix paths before shipping. Some teams wired the
harness as &lt;code&gt;BUILD.BAT&lt;/code&gt; final step with &lt;code&gt;if errorlevel 1&lt;/code&gt; to fail the build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Checksum discipline:&lt;/strong&gt; Store MD5 or CRC of each &lt;code&gt;.BGI&lt;/code&gt; and &lt;code&gt;.CHR&lt;/code&gt; in a
manifest. When field reports &amp;ldquo;weird corruption&amp;rdquo; or mode errors, compare
checksums to rule out truncated or swapped files. A swapped &lt;code&gt;CGA.BGI&lt;/code&gt; and
&lt;code&gt;EGAVGA.BGI&lt;/code&gt; (e.g. misnamed copies) produces &lt;code&gt;grInvalidDriver&lt;/code&gt; or garbled
output; checksums catch that quickly. Run checksum verification as part of
the build pipeline: after copying assets to &lt;code&gt;RELEASE\&lt;/code&gt;, compute checksums and
append to a &lt;code&gt;MANIFEST.TXT&lt;/code&gt;; archive that manifest with the release. During
support triage, ask the user to run a simple checksum tool (or provide a tiny
.COM that prints file sizes) and compare against the manifest — mismatches
point to installer bugs, disk errors, or manual file replacements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Floppy and installer considerations:&lt;/strong&gt; If distributing on floppies, put
&lt;code&gt;MYAPP.EXE&lt;/code&gt; on disk 1 and &lt;code&gt;BGI\&lt;/code&gt; contents on the same or next disk. Installer
scripts should copy &lt;code&gt;BGI\&lt;/code&gt; into the target directory and set current-directory
expectations in a README. Avoid assuming users will run from a subdirectory;
many double-click or type &lt;code&gt;MYAPP&lt;/code&gt; from &lt;code&gt;C:\GAMES\&lt;/code&gt; and expect &lt;code&gt;.\BGI&lt;/code&gt; to mean
&lt;code&gt;C:\GAMES\BGI&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;are-bgi-file-formats-fully-documented&#34;&gt;Are BGI file formats fully documented?&lt;/h2&gt;
&lt;p&gt;Honest answer: not in a stable, complete way that is safe to treat as universal
across all TP/BP-era variants. You can inspect BGI bytes and infer structure,
but production workflows historically relied on Borland-provided drivers and
APIs, not custom byte-level authoring from scratch. Third-party efforts (e.g.
SDL_bgi, Free Pascal Graph unit) have reverse-engineered portions of the
format for compatibility; those sources may help if you need to validate or
debug driver files, but do not assume full specification coverage.&lt;/p&gt;
&lt;p&gt;What you can reliably do today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;verify driver/font assets exist and match expected set&lt;/li&gt;
&lt;li&gt;checksum assets as release artifacts&lt;/li&gt;
&lt;li&gt;use diagnostic harnesses to confirm runtime load path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Diagnostics pitfall:&lt;/strong&gt; Do not assume a BGI file is valid just because it
exists. A truncated or corrupted file can produce &lt;code&gt;grInvalidDriver&lt;/code&gt; or
unpredictable behavior. If you suspect file integrity, compare size and
checksum against a known-good copy from your toolchain installation.&lt;/p&gt;
&lt;h2 id=&#34;how-are-bgi-drivers-created-practical-answer&#34;&gt;&amp;ldquo;How are BGI drivers created?&amp;rdquo; practical answer&lt;/h2&gt;
&lt;p&gt;Three realistic paths:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use stock Borland drivers&lt;/strong&gt; (most common historical path). Ship &lt;code&gt;EGAVGA.BGI&lt;/code&gt;,
&lt;code&gt;CGA.BGI&lt;/code&gt;, etc. from your TP/BP installation. Ensure version consistency:
TP5 drivers are not guaranteed compatible with BP7 Graph unit and vice versa.
When in doubt, use drivers from the same toolchain that produced &lt;code&gt;GRAPH.TPU&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Link stock drivers into executable&lt;/strong&gt; for deployment robustness. Convert
with &lt;code&gt;BINOBJ&lt;/code&gt;, link, register. Same version-pinning rule applies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Author custom driver&lt;/strong&gt; only if you own/understand ABI details and tooling.
The BGI format includes device-specific entry points, mode tables, and
drawing primitives. Third-party documentation (e.g. from replacement BGI
projects) exists but varies in accuracy. Treat custom drivers as a separate
maintenance burden.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Path 3 is advanced reverse-engineering/ABI work. It is possible, but not the
right default for project delivery unless driver capabilities are missing.&lt;/p&gt;
&lt;h2 id=&#34;bgi-startup-diagnostics-harness-must-have&#34;&gt;BGI startup diagnostics harness (must-have)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program BgiDiag;
uses Graph, Crt;
var
  gd, gm, gr: Integer;
begin
  gd := Detect;
  InitGraph(gd, gm, &amp;#39;.\BGI&amp;#39;);
  gr := GraphResult;
  Writeln(&amp;#39;Driver=&amp;#39;, gd, &amp;#39; Mode=&amp;#39;, gm, &amp;#39; GraphResult=&amp;#39;, gr);
  if gr = grOk then
  begin
    SetColor(15);
    OutTextXY(8, 8, &amp;#39;BGI init OK&amp;#39;);
    Line(0, 0, GetMaxX, GetMaxY);
    ReadKey;
    CloseGraph;
  end
  else
    Writeln(&amp;#39;Init failed. Check path, files, memory.&amp;#39;);
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Run this before debugging your game engine. It isolates path/driver faults from
rendering logic faults. Keep it as a separate program in your tree; do not
embed it inside the main app, because you want to run it in isolation when
the full app crashes before any output. A harness that runs to completion
proves the BGI stack works; if the harness fails, fix that before debugging
renderer code. Extend the harness when you encounter new failure modes: add a
font-load test if &lt;code&gt;grFontNotFound&lt;/code&gt; appears in the field, or a &lt;code&gt;Detect&lt;/code&gt;-then-
forced-mode variant if adapter detection is unreliable. The harness becomes
your regression suite for the BGI layer; document each variant and when to
run it. Some teams maintained a &lt;code&gt;BGIDIAG.EXE&lt;/code&gt; in the release package so
support could ask users to run it and report the printed codes — a single
number (&lt;code&gt;GraphResult&lt;/code&gt;) often suffices to distinguish path, memory, and driver
issues without requiring logs or repro steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Triage procedure with the harness:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run from project root with &lt;code&gt;.\BGI&lt;/code&gt; containing drivers — expect &lt;code&gt;grOk&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run from same directory but rename &lt;code&gt;BGI&lt;/code&gt; to &lt;code&gt;BGI_BACKUP&lt;/code&gt; — expect
&lt;code&gt;grFileNotFound&lt;/code&gt;; confirm printed code matches.&lt;/li&gt;
&lt;li&gt;Run from a directory without &lt;code&gt;BGI&lt;/code&gt; subfolder — expect &lt;code&gt;grFileNotFound&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On a TSR-heavy config (mouse, network driver, etc.), run harness — if
&lt;code&gt;grNoLoadMem&lt;/code&gt;, document minimum free conventional memory for your build.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Important TP5 behavior: &lt;code&gt;GraphResult&lt;/code&gt; resets to zero after it has been called.
Store it in a variable once and evaluate that variable.&lt;/p&gt;
&lt;h2 id=&#34;font-handling-is-a-real-subsystem&#34;&gt;Font handling is a real subsystem&lt;/h2&gt;
&lt;p&gt;If UI layout depends on font metrics, &lt;code&gt;.CHR&lt;/code&gt; assets are first-class artifacts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;version and checksum them&lt;/li&gt;
&lt;li&gt;package with same discipline as executables&lt;/li&gt;
&lt;li&gt;test fallback behavior explicitly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Silent fallback to default font can break coordinates, clipping, and hit zones.
A menu rendered with &lt;code&gt;GOTH.CHR&lt;/code&gt; has different line heights than the default;
if the font fails to load, text may overlap or clip incorrectly.&lt;/p&gt;
&lt;p&gt;TP5 adds two separate extension points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;InstallUserFont&lt;/code&gt; (register by name/file path)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RegisterBGIfont&lt;/code&gt; (register loaded or linked-in font pointer)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As with drivers, registration must be done before normal graphics workflow relies
on those resources. After &lt;code&gt;SetTextStyle(...)&lt;/code&gt;, check &lt;code&gt;GraphResult&lt;/code&gt; if the font
was user-installed; &lt;code&gt;grFontNotFound&lt;/code&gt; or &lt;code&gt;grNoFontMem&lt;/code&gt; indicate load failure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory interaction:&lt;/strong&gt; Stroked fonts are loaded into heap. A large &lt;code&gt;.CHR&lt;/code&gt; plus
graphics buffer plus overlay buffer can exhaust conventional memory. If
&lt;code&gt;grNoFontMem&lt;/code&gt; appears only with certain fonts, try smaller fonts or linked-font
approach for the critical path. Font packaging parallels driver packaging:
ship only the fonts your UI actually uses, version-pin them to your toolchain,
and include checksums in the release manifest. A common mistake is bundling
every &lt;code&gt;.CHR&lt;/code&gt; from the TP install &amp;ldquo;for completeness&amp;rdquo; — this bloats the package
and increases the chance of loading the wrong font by typo or path confusion.
If &lt;code&gt;SetTextStyle&lt;/code&gt; references a font that was never loaded or registered, the
unit falls back to default; the fallback is silent, so layout assumptions
(lower height, different metrics) can break. Add an explicit font-load check
after &lt;code&gt;SetTextStyle&lt;/code&gt; for user fonts and log &lt;code&gt;GraphResult&lt;/code&gt; during development.&lt;/p&gt;
&lt;h2 id=&#34;bgi--overlays--memory-budget-interaction&#34;&gt;BGI + overlays + memory budget interaction&lt;/h2&gt;
&lt;p&gt;Graphics initialization and overlays interact with memory pressure. If startup
becomes unstable after enabling overlays or TSR-heavy profiles, validate:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;available memory headroom before &lt;code&gt;InitGraph&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;overlay manager buffer size (&lt;code&gt;OvrSetBuf&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;order of subsystem initialization&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Treat graphics bugs and memory bugs as potentially coupled until proven otherwise.
Memory interplay with overlays is the most common source of &amp;ldquo;works in dev,
fails in release&amp;rdquo; BGI bugs: the overlay manager allocates a contiguous buffer
from the heap; Graph allocates its own buffers from the same heap. If the overlay
buffer is carved out first, Graph gets what remains; if Graph allocates first
and overlays later try to grow, the heap can be fragmented or exhausted.
&lt;code&gt;grNoLoadMem&lt;/code&gt; often appears when overlay and Graph compete for the same pool
without a clear initialization order.&lt;/p&gt;
&lt;p&gt;TP5 memory detail: Graph uses heap for graphics buffer, loaded drivers, and
loaded stroked fonts (unless linked/registered path is used). This is why
overlay buffer sizing (&lt;code&gt;OvrSetBuf&lt;/code&gt;) and &lt;code&gt;InitGraph&lt;/code&gt; order can conflict.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Order rule:&lt;/strong&gt; Call &lt;code&gt;OvrSetBuf&lt;/code&gt; (and &lt;code&gt;OvrInit&lt;/code&gt;) &lt;strong&gt;before&lt;/strong&gt; &lt;code&gt;InitGraph&lt;/code&gt;. The
overlay manager carves its buffer from the heap; Graph then allocates from what
remains. Reversing the order can leave insufficient room for either. A typical
failure: &lt;code&gt;InitGraph&lt;/code&gt; succeeds, then &lt;code&gt;OvrSetBuf&lt;/code&gt; shrinks the heap, and a later
overlay load or Graph operation fails with &lt;code&gt;grNoLoadMem&lt;/code&gt; or overlay error. The
fix is to establish overlay buffer first, then let Graph allocate from the
remaining free memory.&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(gd, gm, &amp;#39;.\BGI&amp;#39;);
gr := GraphResult;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Memory budgeting:&lt;/strong&gt; On a 640 KB DOS machine, TSRs and DOS typically consume
50–150 KB. Your app gets the rest. A VGA buffer (640×480×1 byte) is ~300 KB;
EGAVGA.BGI adds tens of KB when loaded; stroked fonts add more. If you use
overlays, their buffer comes from the same pool. Document a minimum free-memory
requirement (e.g. &amp;ldquo;400 KB free conventional memory&amp;rdquo;) and test at that threshold.
Boot with a minimal CONFIG.SYS and AUTOEXEC.BAT to simulate a memory-tight
environment; if your app runs there, it will usually run on richer systems. This
simple test catches many &amp;ldquo;works on my machine&amp;rdquo; deployment failures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overlay–Graph allocation interplay:&lt;/strong&gt; The heap layout after &lt;code&gt;OvrSetBuf&lt;/code&gt; and
&lt;code&gt;InitGraph&lt;/code&gt; is toolchain- and order-dependent. A rough rule of thumb: VGA
graphics buffer (~300 KB) + EGAVGA driver (~30–50 KB when loaded) + one stroked
font (~20–40 KB) leaves little for overlays on a 640 KB system with 400 KB free.
If you use both overlays and Graph, establish the overlay buffer first with a
conservative size, then init Graph; measure free memory before and after each
step during integration. Some teams added a startup banner (&amp;ldquo;Free mem: XXXXX&amp;rdquo;)
before &lt;code&gt;InitGraph&lt;/code&gt; to catch regressions. &lt;strong&gt;Uncertainty note:&lt;/strong&gt; Exact allocation
order and sizes can vary between TP5, TP6, and BP7; when in doubt, instrument
and measure on your target configuration.&lt;/p&gt;
&lt;h2 id=&#34;debugging-rendering-failures-on-real-dos-systems&#34;&gt;Debugging rendering failures on real DOS systems&lt;/h2&gt;
&lt;p&gt;When graphics fail in the field but work in development, systematic triage
narrows the cause. Use a &lt;strong&gt;rendering triage loop&lt;/strong&gt;: run the diagnostic harness
first; if it passes, the fault is in application rendering logic or asset
loading, not BGI init. If the harness fails, iterate on path, memory, or driver
until it passes, then return to the full app. Do not debug a complex renderer
while BGI fundamentals are still failing — you will waste time chasing symptoms
(e.g. &amp;ldquo;Line draws wrong&amp;rdquo;) that stem from an earlier init or mode problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Release verification on real hardware:&lt;/strong&gt; Before signing off a build, run the
full checklist on at least one physical DOS machine (or a well-configured
emulator that matches period behavior). Boot from floppy or minimal HD; run from
&lt;code&gt;A:\&lt;/code&gt;, &lt;code&gt;C:\GAMES&lt;/code&gt;, and a nested subdirectory; try with and without common TSRs
(mouse, sound driver). Known problematic configurations include: VGA clones with
nonstandard BIOS mode tables, EGA systems with 256 KB vs 64 KB variants, and
machines with &amp;lt; 400 KB free conventional memory. Document which adapter and
memory profile you verified; when field reports arrive, compare against that
baseline. A build that has never been run on real hardware is a risk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Triage steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Black screen, no message:&lt;/strong&gt; Program may be exiting before any output.
Add a &lt;code&gt;Writeln(&#39;Starting...&#39;)&lt;/code&gt; before &lt;code&gt;InitGraph&lt;/code&gt;; if it never appears,
the crash is earlier (e.g. overlay init, missing &lt;code&gt;.OVR&lt;/code&gt;). On some DOS
configurations, mode switch can also blank the screen before text output
is visible; redirect output to a file (&lt;code&gt;MYAPP &amp;gt; LOG.TXT 2&amp;gt;&amp;amp;1&lt;/code&gt;) to confirm
whether any text was produced.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Black screen, message appeared then vanished:&lt;/strong&gt; Mode switch may have
failed, or the program exited immediately. Ensure &lt;code&gt;GraphResult&lt;/code&gt; is checked
and stored before any cleanup; add &lt;code&gt;ReadKey&lt;/code&gt; or &lt;code&gt;Delay&lt;/code&gt; before &lt;code&gt;CloseGraph&lt;/code&gt;
in harness to confirm. If the message flashes too quickly to read, write
it to a file as well.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrong resolution or garbled display:&lt;/strong&gt; Driver/mode mismatch. &lt;code&gt;Detect&lt;/code&gt; may
pick a different mode on different adapters; log &lt;code&gt;gd&lt;/code&gt; and &lt;code&gt;gm&lt;/code&gt; and
compare to adapter documentation. Force a known mode (e.g. &lt;code&gt;gd := VGA; gm := VGAHi&lt;/code&gt;) for compatibility testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Works once, fails on second run:&lt;/strong&gt; TSR or environment pollution. Reboot
to clean state; disable TSRs one by one. Some drivers leave video state
inconsistent after &lt;code&gt;CloseGraph&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;grNoLoadMem on target only:&lt;/strong&gt; Conventional memory too low. Run &lt;code&gt;MEM&lt;/code&gt;
before app; compare to dev machine. Reduce overlay buffer or ship linked
driver build.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Keep a triage log: adapter type, driver set, free memory, TSR list, and exact
&lt;code&gt;GraphResult&lt;/code&gt; value. Reproducible cases go into the test matrix. When a new
symptom appears (e.g. &amp;ldquo;screen flickers then goes black&amp;rdquo;), add a minimal
reproducer to the harness: if you can trigger it there, debug there; if only
the full app exhibits it, the cause is likely in overlay loading order, asset
sequencing, or interaction with game/UI logic. This divide-and-conquer approach
keeps triage loops short and deterministic.&lt;/p&gt;
&lt;h2 id=&#34;team-checklists-and-release-hardening&#34;&gt;Team checklists and release hardening&lt;/h2&gt;
&lt;p&gt;Before release, the team should complete:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pre-build:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Unit search path, object path, and BGI asset path documented and
version-pinned&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Build script produces deterministic output (clean build, no stale artifacts)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Build:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; All required &lt;code&gt;.BGI&lt;/code&gt; and &lt;code&gt;.CHR&lt;/code&gt; copied to release layout&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Diagnostic harness runs successfully from release directory&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Checksums recorded for &lt;code&gt;.EXE&lt;/code&gt;, &lt;code&gt;.OVR&lt;/code&gt; (if used), &lt;code&gt;.BGI&lt;/code&gt;, &lt;code&gt;.CHR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Post-build verification:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Test from directory different from source (e.g. &lt;code&gt;C:\TEST\&lt;/code&gt;, &lt;code&gt;A:\&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Intentionally remove one driver, run app — verify error message, no crash&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Test with overlay file missing (if applicable) — verify controlled exit&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; One memory-stressed run (e.g. with &lt;code&gt;MEM&lt;/code&gt; reporting &amp;lt; 400 KB free)&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Run diagnostic harness from same release layout; assert it reports &lt;code&gt;grOk&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; If distributing on floppy: boot from disk 1, run from &lt;code&gt;A:\&lt;/code&gt;, confirm BGI
path resolves correctly (e.g. &lt;code&gt;A:\BGI\&lt;/code&gt; when app is on &lt;code&gt;A:\&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Release package:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; README includes BGI path requirements and minimum memory&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; Build manifest (checksums, compiler version, options) archived with
artifacts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Expected outcome: actionable startup message on any failure, never black-screen
ambiguity. When a user reports &amp;ldquo;does not work,&amp;rdquo; the checklist gives you
questions to ask (adapter, path, memory, exact error text) instead of blind
guesswork. Teams that shipped without this discipline often spent hours on
support calls trying to reproduce &amp;ldquo;black screen&amp;rdquo; with no data. A single
&lt;code&gt;Writeln(&#39;BGI error: &#39;, gr)&lt;/code&gt; before &lt;code&gt;Halt(1)&lt;/code&gt; can save days of debugging.&lt;/p&gt;
&lt;p&gt;Useful TP5 &lt;code&gt;InitGraph&lt;/code&gt; failure codes to log:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;grNotDetected&lt;/code&gt; (&lt;code&gt;-2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grFileNotFound&lt;/code&gt; (&lt;code&gt;-3&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grInvalidDriver&lt;/code&gt; (&lt;code&gt;-4&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grNoLoadMem&lt;/code&gt; (&lt;code&gt;-5&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grInvalidMode&lt;/code&gt; (&lt;code&gt;-10&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;where-to-go-deeper&#34;&gt;Where to go deeper&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/&#34;&gt;Turbo Pascal BGI Tutorial: Dynamic Drivers, Linked Drivers, and Diagnostic Harnesses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/mode-13h-graphics-in-turbo-pascal/&#34;&gt;Mode 13h Graphics in Turbo Pascal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-1-planar-memory-model/&#34;&gt;Mode X Part 1: Planar Memory Model&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next:&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-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Turbo Pascal Toolchain, Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal Toolchain, Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-5-from-6-to-7-compiler-linker-and-language-growth/</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-5-from-6-to-7-compiler-linker-and-language-growth/</guid>
      <description>&lt;p&gt;Parts 1-4 covered workflow, artifacts, overlays, and BGI integration. This last
part goes inside the compiler/language boundary: memory assumptions, type layout,
calling conventions, and assembler integration from TP6-era practice to TP7/BP7
scope.&lt;/p&gt;
&lt;h3 id=&#34;structure-map&#34;&gt;Structure map&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Version framing&lt;/strong&gt; — TP6 vs TP7/BP7 scope, continuity and deltas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution model&lt;/strong&gt; — real-mode assumptions, segmentation, near/far&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data type layout&lt;/strong&gt; — size table, alignment, layout probe harness&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory layout consequences&lt;/strong&gt; — ShortString, sets, records, arrays&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Procedures vs functions&lt;/strong&gt; — semantics and ABI implications&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Calling conventions&lt;/strong&gt; — stack layout, parameter order, return strategy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compiler directives&lt;/strong&gt; — policy, safety controls, project-wide usage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assembler integration&lt;/strong&gt; — inline blocks, external OBJ, boundary contracts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TP6→TP7 migration&lt;/strong&gt; — pipeline evolution, artifact implications, language growth&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Protected mode and OOP&lt;/strong&gt; — BP7 context, object layout, VMT considerations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migration checklist&lt;/strong&gt; — risk controls, test loops, regression traps, common pitfalls&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;version-framing-what-changed-and-what-stayed-stable&#34;&gt;Version framing: what changed and what stayed stable&lt;/h2&gt;
&lt;p&gt;The TP6 to TP7 shift was less a language revolution and more an expansion of
operational surface:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;larger project/tooling workflows became easier&lt;/li&gt;
&lt;li&gt;artifact and mixed-language integration became more central&lt;/li&gt;
&lt;li&gt;language core stayed recognizably Turbo Pascal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the technical model below is largely continuous across this generation, with
feature breadth increasing in later packaging. Borland Pascal 7 (BP7) extended
this further with protected-mode compilation, built-in debugging support, and
richer IDE integration, while TP7 remained primarily a real-mode product.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version-specific nuances&lt;/strong&gt; — TP7.0 (1990) stabilized the TP6 object model and
improved unit compilation speed. TP7.1 addressed bugs and refinements; some
teams held at 7.0 for compatibility with shared codebases. BP7 (1992) bundled
Turbo Debugger, expanded the RTL, and introduced DPMI target support. Exact
behavior of directives like &lt;code&gt;{$G+}&lt;/code&gt; (80286 instructions), &lt;code&gt;{$A+}&lt;/code&gt; (record
alignment), and codegen choices can vary between these builds; when precise
version behavior matters, treat claims here as a starting point and validate
against your toolchain.&lt;/p&gt;
&lt;h2 id=&#34;execution-model-assumptions-the-non-negotiables&#34;&gt;Execution model assumptions (the non-negotiables)&lt;/h2&gt;
&lt;p&gt;Real-mode DOS assumptions drive everything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Segmented memory model&lt;/strong&gt; — 64 KB segments, selector:offset addressing, 20-bit physical address space. DS usually points at the program’s data; SS at the stack; CS at code. Overlays swap code segments in and out of a single overlay area.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;16-bit register-centric calling paths&lt;/strong&gt; — AX, BX, CX, DX, SI, DI, BP, SP; segment registers CS, DS, SS, ES.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Near vs far distinctions&lt;/strong&gt; — near calls use same segment (16-bit offset), far calls require segment:offset (32-bit); overlay units demand far entry points.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conventional memory pressure&lt;/strong&gt; — first 640 KB shared by DOS, TSRs, drivers, and your program; overlays and heap compete for the same pool.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The linker’s choice of memory model (Tiny, Small, Medium, Large, Huge) constrains code and data segment layout. TP6 and TP7 both default to Small model in typical configurations: one code segment, one data segment, with near pointers. Tiny folds code and data into one segment (for .COM output); Medium allows multiple code segments (far code, near data); Large/Huge allow multiple data segments with far pointers—changing pointer size from 2 to 4 bytes. Switching to Large model changes pointer sizes and call conventions; map-file analysis becomes essential when hunting link errors or unexpected runtime behavior.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Artifact implications&lt;/strong&gt; — Small model yields a single .EXE with code and data
in separate segments. Overlays add .OVR files; each overlay is its own code
segment loaded on demand. The linker produces a .MAP file listing segment
addresses and public symbols; use it to verify overlay boundaries and diagnose
&amp;ldquo;fixup overflow&amp;rdquo; or segment-order issues. Segment order in the map (CODE, DATA,
overlay segments) affects load addresses; changing unit compile order can shift
symbols and break code that assumes fixed offsets. A typical map lists segments
in load order with start/stop addresses; overlays appear as named segments with
their size—verify overlay sizes match expectations before debugging load
failures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data layout and ABI&lt;/strong&gt; — Record fields, set bit layouts, and string formats
are stable within a compiler version but can differ across TP6, TP7, and BP7.
When sharing binary structures (e.g., files or shared memory) between programs
built with different toolchains, define a canonical layout and validate with
layout probes. Ignoring these constraints while reading Pascal source leads to
wrong performance and ABI conclusions. A layout-probe program that prints
&lt;code&gt;SizeOf&lt;/code&gt; for all shared types, run under each toolchain, gives a quick
compatibility report before committing to a cross-toolchain design.&lt;/p&gt;
&lt;h2 id=&#34;data-type-layout-practical-table&#34;&gt;Data type layout: practical table&lt;/h2&gt;
&lt;p&gt;Common TP-era sizes in real-mode profiles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Byte&lt;/code&gt;: 1 byte&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ShortInt&lt;/code&gt;: 1 byte&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Word&lt;/code&gt;: 2 bytes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Integer&lt;/code&gt;: 2 bytes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LongInt&lt;/code&gt;: 4 bytes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Char&lt;/code&gt;: 1 byte&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Boolean&lt;/code&gt;: 1 byte&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Pointer&lt;/code&gt;: 4 bytes (segment:offset in real mode)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;String[N]&lt;/code&gt;: &lt;code&gt;N+1&lt;/code&gt; bytes (length byte + payload)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Floating-point and extended numeric types (&lt;code&gt;Real&lt;/code&gt;, &lt;code&gt;Single&lt;/code&gt;, &lt;code&gt;Double&lt;/code&gt;,
&lt;code&gt;Extended&lt;/code&gt;, &lt;code&gt;Comp&lt;/code&gt;) exist with version/profile-specific behavior and FPU/emulation
settings, so treat exact codegen cost as configuration dependent. With &lt;code&gt;{$N+}&lt;/code&gt;,
the compiler uses native FPU instructions; with &lt;code&gt;{$N-}&lt;/code&gt;, software emulation
(via runtime library) is typical. &lt;code&gt;Real&lt;/code&gt; is typically 6-byte BCD in older
profiles and can map to &lt;code&gt;Single&lt;/code&gt; or a software type in others—verify in your
build. &lt;code&gt;Extended&lt;/code&gt; is 10 bytes (80-bit); &lt;code&gt;Comp&lt;/code&gt; is 8-byte integer format often
used for currency. Set types use one bit per element; &lt;code&gt;set of 0..7&lt;/code&gt; is 1 byte,
&lt;code&gt;set of 0..15&lt;/code&gt; is 2 bytes, up to 32 bytes for &lt;code&gt;set of 0..255&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alignment and packing&lt;/strong&gt; — Turbo Pascal generally packs record fields without
inserting padding; fields align to their natural size (1, 2, 4 bytes). The
&lt;code&gt;{$A+/-}&lt;/code&gt; (Align Records) directive, where available, can change this—&lt;code&gt;{$A+}&lt;/code&gt; may
align record fields to word boundaries for faster access on some processors.
Packed records (&lt;code&gt;packed record&lt;/code&gt;) minimize size at potential performance cost.
For structures crossing the Pascal–C–assembly boundary, explicit layout
verification is mandatory; C struct alignment rules often differ.&lt;/p&gt;
&lt;h3 id=&#34;quick-layout-probe-harness&#34;&gt;Quick layout probe harness&lt;/h3&gt;
&lt;p&gt;If binary layout matters, measure your exact compiler profile:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;program LayoutProbe;
type
  TRec = record
    B: Byte;
    W: Word;
    L: LongInt;
  end;
  TPackedRec = packed record
    B: Byte;
    W: Word;
  end;
begin
  Writeln(&amp;#39;SizeOf(Integer)=&amp;#39;, SizeOf(Integer));
  Writeln(&amp;#39;SizeOf(Pointer)=&amp;#39;, SizeOf(Pointer));
  Writeln(&amp;#39;SizeOf(String[20])=&amp;#39;, SizeOf(String[20]));
  Writeln(&amp;#39;SizeOf(TRec)=&amp;#39;, SizeOf(TRec));
  Writeln(&amp;#39;SizeOf(TPackedRec)=&amp;#39;, SizeOf(TPackedRec));
  Writeln(&amp;#39;SizeOf(Single)=&amp;#39;, SizeOf(Single), &amp;#39; SizeOf(Real)=&amp;#39;, SizeOf(Real));
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Expected outcome: concrete numbers for your environment. Never assume layout
from memory when ABI compatibility is at stake.&lt;/p&gt;
&lt;h2 id=&#34;memory-layout-consequences-developers-felt-daily&#34;&gt;Memory layout consequences developers felt daily&lt;/h2&gt;
&lt;h3 id=&#34;shortstring-behavior&#34;&gt;ShortString behavior&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;String&lt;/code&gt; in classic Turbo Pascal is a short string (length-prefixed), not a
null-terminated C string. Consequences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O(1) length read via byte 0&lt;/li&gt;
&lt;li&gt;max 255 characters; &lt;code&gt;String[80]&lt;/code&gt; is 81 bytes&lt;/li&gt;
&lt;li&gt;direct interop with C APIs needs conversion: either build a null-terminated
copy or pass &lt;code&gt;Str[1]&lt;/code&gt; and ensure the C side respects the length byte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simple conversion helper for C library calls (TP7&amp;rsquo;s Strings unit has
&lt;code&gt;StrPCopy&lt;/code&gt;; this illustrates the manual pattern):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure PascalToCString(const S: String; var Buf; MaxLen: Byte);
var
  I: Byte;
  P: ^Char;
begin
  P := @Buf;
  I := 0;
  while (I &amp;lt; S[0]) and (I &amp;lt; MaxLen - 1) do begin
    P^ := S[I + 1];
    Inc(P); Inc(I);
  end;
  P^ := #0;
end;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;set-and-record-layout&#34;&gt;Set and record layout&lt;/h3&gt;
&lt;p&gt;Set/record memory footprint is compact but sensitive to declared ranges and
packing decisions. A &lt;code&gt;set of 0..255&lt;/code&gt; consumes up to 32 bytes (one bit per
element); smaller ranges use fewer bytes (e.g., &lt;code&gt;set of 0..15&lt;/code&gt; is 2 bytes).
Record alignment follows the directive and packing mode. Bit ordering within
set bytes is implementation-defined; when exchanging set values with C or
assembly, document and test the mapping. If binary compatibility matters,
verify layout with &lt;code&gt;SizeOf&lt;/code&gt; tests in a dedicated compatibility harness. TP6
and TP7 generally match on these layouts, but mixed toolchains (e.g., C object
modules) may introduce padding differences.&lt;/p&gt;
&lt;h3 id=&#34;arrays&#34;&gt;Arrays&lt;/h3&gt;
&lt;p&gt;Arrays are contiguous. High-throughput code benefits from locality, but segment
boundaries and index range checks (if enabled) influence speed and safety.
Multi-dimensional arrays are stored in row-major order. Static arrays and
open-array parameters have different calling semantics: open arrays pass a
hidden length (typically as the last parameter or in a known slot), which
affects the ABI at procedure boundaries. Example:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;procedure Process(const Arr: array of Integer);  { Arr: ptr + hidden High(Len) }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;String parameters pass by reference (address of the length byte); value
parameters of record type may be copied onto the stack or via a hidden pointer,
depending on size—records larger than a few bytes often use a hidden &lt;code&gt;var&lt;/code&gt;
parameter to avoid stack bloat. When interfacing with assembly, document how
each parameter type is passed.&lt;/p&gt;
&lt;h2 id=&#34;procedures-vs-functions-not-just-syntax&#34;&gt;Procedures vs functions: not just syntax&lt;/h2&gt;
&lt;p&gt;Difference in language semantics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;procedure&lt;/code&gt;: action with no return value&lt;/li&gt;
&lt;li&gt;&lt;code&gt;function&lt;/code&gt;: returns value and can appear in expressions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Difference in engineering use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;procedures often model side-effecting operations&lt;/li&gt;
&lt;li&gt;functions often model value computation or query paths&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In low-level interop, function return strategy and calling convention details
matter for ABI compatibility with external objects. Scalars (Byte, Word, Integer,
LongInt, pointers) typically return in registers: Byte in AL, Word/Integer in
AX, LongInt in DX:AX (high word in DX, low in AX), pointers in DX:AX (segment
in DX, offset in AX). Larger types (records, arrays) may use a hidden &lt;code&gt;var&lt;/code&gt;
parameter or a caller-allocated temporary; the threshold and mechanism vary by
version and type size—commonly, records exceeding 4 bytes use a hidden
first parameter for the return buffer.&lt;/p&gt;
&lt;p&gt;When calling or implementing assembly routines that mimic Pascal functions,
match the return mechanism or corruption is likely. A function declared
&lt;code&gt;external&lt;/code&gt; in Pascal must place its return value where the Pascal caller
expects it; an inline &lt;code&gt;asm&lt;/code&gt; block that computes a &lt;code&gt;LongInt&lt;/code&gt; return should
leave the result in DX:AX before the block ends. For &lt;code&gt;Word&lt;/code&gt; returns, ensure
the high byte of AX is clean if the caller extends the value.&lt;/p&gt;
&lt;h2 id=&#34;calling-conventions-and-abi-boundaries&#34;&gt;Calling conventions and ABI boundaries&lt;/h2&gt;
&lt;p&gt;Turbo Pascal default calling convention differs from C conventions commonly used
by external modules. Pascal uses left-to-right parameter push order; C typically
uses right-to-left (cdecl). Pascal procedures usually clean the stack (ret N);
C callers often clean (cdecl). Name mangling can differ: Pascal may export
symbols with no decoration or with a leading underscore; C compilers vary.
At integration boundaries, define explicitly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Parameter order&lt;/strong&gt; — left-to-right (Pascal) vs right-to-left (C)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stack cleanup responsibility&lt;/strong&gt; — callee (Pascal-style) vs caller (cdecl)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Near vs far procedure model&lt;/strong&gt; — must match declaration and link unit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Value return mechanism&lt;/strong&gt; — register vs stack for large returns&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If any of these is ambiguous, &amp;ldquo;link succeeds but runtime breaks&amp;rdquo; is predictable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stack frame layout&lt;/strong&gt; — The compiler sets up BP as a frame pointer; parameters
are accessed via positive offsets from BP. For a near call, the return address
occupies 2 bytes (IP only); for a far call, 4 bytes (CS:IP). Parameter offsets
shift accordingly. Typical Pascal caller view: parameters pushed left-to-right,
then call. Callee sees highest parameter at lowest address. Example frame for
&lt;code&gt;Proc(A: Word; B: LongInt)&lt;/code&gt; (near call):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ Stack grows down. After PUSH BP; MOV BP, SP: BP+2 = ret addr, BP+4 = first param }
{ BP+4 = A (Word), BP+6 = B low, BP+8 = B high. Callee uses RET 6. }
{ For far call: BP+4 = CS, BP+6 = IP; first param at BP+8. }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Near procedures use &lt;code&gt;CALL near ptr&lt;/code&gt; and &lt;code&gt;RET&lt;/code&gt;; far procedures use &lt;code&gt;CALL far ptr&lt;/code&gt;
and &lt;code&gt;RETF&lt;/code&gt;. The callee must not change BP, SP, or segment registers except as
permitted by the convention. For external C routines, use &lt;code&gt;cdecl&lt;/code&gt; or equivalent
where the object was built with C; otherwise stack imbalance or wrong parameter
binding occurs. Inline assembly that calls external code must replicate the
expected convention:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;function CStrLen(P: PChar): Word; cdecl; external &amp;#39;CLIB&amp;#39;;
// or, if linking C OBJ directly:
{$L mystr.obj}
function CStrLen(P: PChar): Word; cdecl; external;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In mixed-language projects, write one tiny ABI verification test per external
routine family before integrating into real logic—e.g., call with known inputs,
assert expected output. Example harness: a small program that calls
&lt;code&gt;MulAcc(100, 200, 50)&lt;/code&gt;, expects a known result, and exits with code 0 on success;
run it immediately after linking a new assembly module to catch offset or
cleanup mismatches before they surface in production.&lt;/p&gt;
&lt;h2 id=&#34;compiler-directives-as-architecture-controls&#34;&gt;Compiler directives as architecture controls&lt;/h2&gt;
&lt;p&gt;Directives are not cosmetic. They change behavior and generated code.&lt;/p&gt;
&lt;p&gt;Examples frequently used in serious projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{$R+/-}&lt;/code&gt;: range checking — array bounds, subrange&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{$Q+/-}&lt;/code&gt;: overflow checking — integer arithmetic&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{$S+/-}&lt;/code&gt;: stack checking — overflow sentinel&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{$I+/-}&lt;/code&gt;: I/O checking — handle errors vs continue&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{$G+/-}&lt;/code&gt;: 80286+ instructions (in BP7/profile-dependent builds)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{$N+/-}&lt;/code&gt; and related: FPU vs software float&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Exact availability/effects vary by version/profile, so freeze directive policy
per build profile and avoid per-file drift. A project-wide policy file or
leading include can enforce consistency:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{ GLOBAL.INC - lock directives for release build }
{$R+}  { Range check in debug only if you prefer; some teams use {$R-} for ship }
{$Q-}  { Overflow off for speed in release }
{$S+}  { Stack overflow detection recommended }
{$I+}  { I/O errors as exceptions or Check(IOResult) }
{$F+}  { FAR calls if using overlays }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;TP5/TP6/TP7 anchor points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{$F+/-}&lt;/code&gt; (Force FAR Calls) is a &lt;strong&gt;local&lt;/strong&gt; directive with default &lt;code&gt;{$F-}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;{$F-}&lt;/code&gt; state, compiler chooses FAR for interface-declared unit routines
and NEAR otherwise.&lt;/li&gt;
&lt;li&gt;Overlay-heavy programs are advised to use &lt;code&gt;{$F+}&lt;/code&gt; broadly to satisfy overlay
FAR-call requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For &lt;code&gt;{$DEFINE}&lt;/code&gt; and conditional compilation, centralize symbols (e.g.,
&lt;code&gt;DEBUG&lt;/code&gt;, &lt;code&gt;USE_OVERLAYS&lt;/code&gt;) so builds stay reproducible. Avoid scattering
version-specific &lt;code&gt;{$IFDEF}&lt;/code&gt; blocks without documentation. Use &lt;code&gt;{$IFOPT R+}&lt;/code&gt; to
check directive state rather than relying on a separate define when debugging
build configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Directive gotchas&lt;/strong&gt; — &lt;code&gt;{$R+}&lt;/code&gt; adds runtime cost; many shipped builds use
&lt;code&gt;{$R-}&lt;/code&gt;. &lt;code&gt;{$I+}&lt;/code&gt; makes I/O failures raise runtime errors; &lt;code&gt;{$I-}&lt;/code&gt; requires
explicit &lt;code&gt;IOResult&lt;/code&gt; checks. Switching these mid-project causes subtle
bugs. Directive scope matters: a unit&amp;rsquo;s directives do not always affect the main program unless inherited via include. Document the chosen directive set in a README or build script so new contributors do not override them.&lt;/p&gt;
&lt;h2 id=&#34;assembler-integration-paths&#34;&gt;Assembler integration paths&lt;/h2&gt;
&lt;p&gt;Turbo Pascal projects typically used two integration patterns:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Inline assembler blocks&lt;/strong&gt; inside Pascal source — &lt;code&gt;asm ... end&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External object modules&lt;/strong&gt; linked with &lt;code&gt;{$L filename.OBJ}&lt;/code&gt; declarations&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Inline path is great for small hot routines where Pascal symbol visibility helps;
you can reference parameters and locals by name. External path is better for
larger modules and reuse across projects. Both require strict stack discipline
and adherence to the chosen calling convention. Inline blocks cannot use
&lt;code&gt;RET&lt;/code&gt; or &lt;code&gt;RETF&lt;/code&gt; to exit the routine—control must flow to the block end so the
compiler can emit the standard epilogue. For conditional exit, use &lt;code&gt;goto&lt;/code&gt; to a
label after the block or restructure the logic.&lt;/p&gt;
&lt;h3 id=&#34;inline-assembler&#34;&gt;Inline assembler&lt;/h3&gt;
&lt;p&gt;Minimal inline shape:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;function BiosTicks: LongInt;
begin
  asm
    mov ah, $00
    int $1A
    mov word ptr [BiosTicks], dx
    mov word ptr [BiosTicks+2], cx
  end;
end;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This style keeps the function contract in Pascal while performing low-level
work in assembly. It is ideal for small hardware-touching routines. The
compiler generates prologue/epilogue; your inline block must preserve BP, SP,
and segment registers as required. Do not assume register contents on entry
except parameters passed in. DS and SS are typically valid for data/stack
access; ES may be used for string operations or be undefined—save and restore
if you modify it.&lt;/p&gt;
&lt;h3 id=&#34;external-obj-integration&#34;&gt;External OBJ integration&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;{$L FASTMATH.OBJ}
function MulAcc(A, B, C: Word): Word; external;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The OBJ must export a public symbol matching the Pascal identifier. Calling
convention (parameter order, stack cleanup, near/far) must match. If the OBJ
was built with TASM or MASM, ensure the &lt;code&gt;PROC&lt;/code&gt; declaration uses the right
model (e.g., &lt;code&gt;NEAR&lt;/code&gt;/&lt;code&gt;FAR&lt;/code&gt;) and that parameter offsets line up with Pascal’s
push order. Example TASM side for &lt;code&gt;function MulAcc(A, B, C: Word): Word&lt;/code&gt;:&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;span class=&#34;lnt&#34;&gt;14
&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-asm&#34; data-lang=&#34;asm&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;.MODEL&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;small&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;na&#34;&gt;.CODE&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;nf&#34;&gt;PUBLIC&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;MulAcc&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;nf&#34;&gt;MulAcc&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;PROC&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;nf&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;bp&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;nf&#34;&gt;mov&lt;/span&gt;  &lt;span class=&#34;no&#34;&gt;bp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;sp&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;nf&#34;&gt;mov&lt;/span&gt;  &lt;span class=&#34;no&#34;&gt;ax&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;bp&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;; A
&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;nf&#34;&gt;mov&lt;/span&gt;  &lt;span class=&#34;no&#34;&gt;bx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;bp&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;; B
&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;nf&#34;&gt;mov&lt;/span&gt;  &lt;span class=&#34;no&#34;&gt;cx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;bp&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;; C
&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;c1&#34;&gt;; ... compute result in AX ...
&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;nf&#34;&gt;pop&lt;/span&gt;  &lt;span class=&#34;no&#34;&gt;bp&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;nf&#34;&gt;ret&lt;/span&gt;  &lt;span class=&#34;mi&#34;&gt;6&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;nf&#34;&gt;MulAcc&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;ENDP&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;nf&#34;&gt;END&lt;/span&gt;&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;Pascal passes A, B, C left-to-right (A at lowest offset); callee cleans with
&lt;code&gt;RET 6&lt;/code&gt;. Mismatch in offset or cleanup causes wrong results or stack crash.
Note: BP+4 assumes a 2-byte return address for near calls; far calls use 4 bytes,
so offsets shift—for the same routine declared &lt;code&gt;far&lt;/code&gt;, A would be at BP+8.
Always verify against the generated Pascal code or map file. A quick sanity
check: compile a trivial Pascal wrapper that calls the external routine with
known values, run it, and assert the result before integrating into production.&lt;/p&gt;
&lt;h3 id=&#34;boundary-contract-checklist&#34;&gt;Boundary contract checklist&lt;/h3&gt;
&lt;p&gt;Before relying on an external routine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;symbol resolves at link (no &amp;ldquo;undefined external&amp;rdquo; or mangling mismatch)&lt;/li&gt;
&lt;li&gt;stack discipline preserved (balanced push/pop, correct &lt;code&gt;ret&lt;/code&gt; form)&lt;/li&gt;
&lt;li&gt;deterministic output under vector tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the third condition fails, ABI mismatch is the first suspect. Add a minimal
harness that calls the routine with known inputs and asserts the result before
integrating into production code. Record the test in the project so future
linker or compiler upgrades can re-validate. Mixed Pascal–C–assembly projects
benefit from a single &amp;ldquo;ABI smoke&amp;rdquo; program that exercises every external
boundary with canned inputs.&lt;/p&gt;
&lt;h2 id=&#34;tp6tp7-migration-pipeline-evolution-and-artifact-implications&#34;&gt;TP6→TP7 migration: pipeline evolution and artifact implications&lt;/h2&gt;
&lt;h3 id=&#34;compiler-and-linker-pipeline&#34;&gt;Compiler and linker pipeline&lt;/h3&gt;
&lt;p&gt;From TP6 to TP7, the pipeline stayed conceptually the same: compile units to OBJ,
link OBJ with RTL and any external modules to EXE. The flow is: source (.PAS) →
compiler (TPC.EXE / TPCX.EXE) → object (.OBJ) → linker (TLINK.EXE) → executable
(.EXE) and optional map (.MAP). Overlay units add an extra overlay manager and
.OVR files produced during linking. Command-line builds typically use TPC with
options for target model and overlays; the IDE invokes the same tools under the
hood. Saving OBJ files from each compile allows incremental linking and faster
iteration, but migration should start from a full clean rebuild.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Behavioral shifts&lt;/strong&gt; — TP7&amp;rsquo;s compiler produced more consistent symbol naming and
improved handling of large unit graphs. The linker remained TLINK; its /m, /s,
and overlay options work similarly across TP6 and TP7, but segment ordering and
fixup resolution can produce different map layouts. When comparing before/after
migration, expect segment addresses to change even when logic is identical.&lt;/p&gt;
&lt;p&gt;What changed was robustness and integration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Larger projects&lt;/strong&gt; — TP7 handled more units and larger dependency graphs
without tripping over internal limits. Map file output and symbol resolution
improved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object file compatibility&lt;/strong&gt; — TP6 OBJs generally link with TP7, but the
reverse is not guaranteed; TP7 may emit slightly different record layouts or
name mangling in edge cases. Recompile from source when migrating, do not
mix TP6 and TP7 object files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RTL and units&lt;/strong&gt; — Standard units (Crt, Dos, Graph, etc.) evolved; some
routines gained parameters or changed behavior. Re-test code that relies on
unit internals. Graph unit BGI handling, Dos unit path parsing, and Crt
screen buffer assumptions are frequent sources of minor incompatibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OBJ linkage&lt;/strong&gt; — TP7’s TLink (or TLINK) remained compatible with TASM/MASM
object format. Mixed Pascal–assembly projects typically compile Pascal to OBJ,
assemble .ASM to OBJ, then link together. Ensure segment naming and model
(SMALL, MEDIUM, etc.) match across all modules. Use &lt;code&gt;PUBLIC&lt;/code&gt; and &lt;code&gt;EXTRN&lt;/code&gt; in
assembly to mirror Pascal&amp;rsquo;s &lt;code&gt;external&lt;/code&gt; declarations; symbol names must match
exactly. A &amp;ldquo;Fixup overflow&amp;rdquo; or &amp;ldquo;Segment alignment&amp;rdquo; error often indicates
model or segment-name mismatch between modules.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;language-and-oop-growth&#34;&gt;Language and OOP growth&lt;/h3&gt;
&lt;p&gt;TP6 introduced objects; TP7 refined them. Object layout (VMT, instance size)
generally remained compatible, but virtual method tables and constructor/destructor
semantics can vary. BP7 added further extensions. For migration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recompile all object-based units under TP7.&lt;/li&gt;
&lt;li&gt;Run targeted tests on inheritance chains and virtual overrides.&lt;/li&gt;
&lt;li&gt;Avoid depending on undocumented VMT layout.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Objects store the VMT pointer at a fixed offset (often the first field); virtual
methods are dispatched through it. When writing assembly that allocates or
manipulates object instances, preserve that layout:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;type
  TBase = object
    X: Integer;
    procedure DoSomething; virtual;
  end;
  PBase = ^TBase;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instance size and VMT offset are compiler-dependent; use &lt;code&gt;SizeOf(TBase)&lt;/code&gt; and
avoid hardcoding. Constructor calls initialize the VMT pointer; manual
allocation (e.g., &lt;code&gt;New&lt;/code&gt; or heap blocks) requires proper init. Descendant objects
add their fields after the parent’s; single inheritance keeps layout predictable.
Multiple inheritance was not part of classic Turbo Pascal objects, so no VMT
merging concerns apply. When passing object instances to assembly, pass the
pointer (^TBase) and treat the first word/dword as the VMT pointer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constructor and destructor order&lt;/strong&gt; — Turbo Pascal objects use &lt;code&gt;Constructor Init&lt;/code&gt; and &lt;code&gt;Destructor Done&lt;/code&gt; (or custom names). Call order matters: base
constructors before derived, destructors in reverse. Failing to call the
destructor on heap-allocated objects leaks memory. TP7 tightened some edge cases
around constructor chaining; if migration reveals odd behavior in object init,
compare TP6 and TP7 object code for the constructor to spot differences.&lt;/p&gt;
&lt;h2 id=&#34;protected-mode-and-bp7-context&#34;&gt;Protected mode and BP7 context&lt;/h2&gt;
&lt;p&gt;Borland Pascal 7 added protected-mode compilation, producing DPMI-compatible
executables that can access extended memory. Key implications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Segment model&lt;/strong&gt; — 32-bit selectors instead of 16-bit segments; pointer
representation and segment arithmetic differ. Code that assumes real-mode
segment:offset layout may fail. Far pointers in protected mode are selector:offset
but the selector is a DPMI descriptor, not a physical segment. Near pointers
remain 32-bit offsets within a segment; the segment limit is 4 GB in 32-bit
mode, changing allocation and pointer-arithmetic assumptions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RTL differences&lt;/strong&gt; — Protected-mode RTL uses DPMI calls for memory and
interrupts; DOS file I/O and system calls go through the DPMI host. Heap
allocation, overlay loading, and BGI driver loading all route differently than
in real mode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assembly interop&lt;/strong&gt; — Inline and external assembly must use 32-bit-safe
patterns; some real-mode tricks (segment manipulation, direct ports) require
different handling. Real-mode &lt;code&gt;int&lt;/code&gt; instructions work via DPMI emulation but
with different semantics for protected-mode interrupts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;OOP in protected mode&lt;/strong&gt; — Object and VMT layout are compatible with real-mode
BP7, but instance allocation and constructor behavior may differ when the RTL
uses DPMI memory services. Virtual method dispatch itself is unchanged;
problems typically arise from code that reads segment values or assumes
physical addresses. If your project stays in real mode, TP7 is sufficient.
Moving to BP7 protected mode is a larger migration: treat it as a separate phase
with dedicated tests. Real-mode TP7 binaries remain the norm for DOS-targeted
applications; BP7 protected-mode targets DPMI-aware environments (e.g., Windows
3.x, OS/2, or standalone DPMI hosts like 386MAX).&lt;/p&gt;
&lt;h2 id=&#34;practical-migration-checklist-technical-not-nostalgic&#34;&gt;Practical migration checklist (technical, not nostalgic)&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;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&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;1) Freeze known-good TP6 artifacts and checksums.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2) Rebuild clean under target TP7/BP7 environment.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3) Compare executable and map deltas.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;4) Re-validate external OBJ ABI assumptions.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;5) Re-test overlays + graphics + TSR-heavy runtime profile.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;6) Lock directives/options into documented profile files.&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;Each step is auditable: (1) gives a baseline; (2) isolates toolchain change;
(3) surfaces size and symbol shifts; (4) catches OBJ/ABI drift; (5) exercises
integration points; (6) prevents future drift from stray directive changes. Run
the checklist in order; skipping (1) or (2) makes later steps harder to
interpret when regressions appear.&lt;/p&gt;
&lt;h3 id=&#34;risk-controls&#34;&gt;Risk controls&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Baseline capture&lt;/strong&gt; — Checksum the TP6 EXE and map before migration;
record build date and compiler version. Store baseline outputs in a known
location; diff tools and checksum utilities should be available for
comparison.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Incremental migration&lt;/strong&gt; — Migrate one unit or subsystem at a time where
possible; isolate changes to reduce debugging scope. Migrate leaf units
(those with no dependencies on other project units) first; then work
inward toward the main program.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fallback&lt;/strong&gt; — Keep TP6 build environment available until TP7 build is
validated. If TP7 regression appears, you can bisect by reverting units.
Preserve TP6 compiler, linker, and RTL paths; document them so the fallback
is reproducible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;test-loops&#34;&gt;Test loops&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Smoke&lt;/strong&gt; — Program starts, minimal user path completes. Include at least
one path that loads overlays and one that uses BGI if the project employs
them; silent failure on init is common.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regression traps&lt;/strong&gt; — Known inputs that produced known outputs under TP6;
re-run and compare under TP7. Capture checksums or golden files for critical
outputs (reports, exports, screenshots). Automate where possible: a batch
script that runs the program with canned input and diffs output against
baseline catches many regressions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boundary tests&lt;/strong&gt; — Overlay load/unload, BGI init/close, TSR hooks, assembly
entry points. Exercise code paths that touch segmented memory or far pointers.
Add a dedicated test that calls every external assembly routine with edge
values (0, -1, max) to uncover ABI mismatches.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;expected-outcome&#34;&gt;Expected outcome&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Same behavior with clarified build policy, or&lt;/li&gt;
&lt;li&gt;Explicit, measurable deltas you can explain and document.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not acceptable: &amp;ldquo;it feels mostly fine&amp;rdquo; without verification. Aim for a
migration report that states: baseline version, target version, checksum
deltas (or &amp;ldquo;identical&amp;rdquo;), test results (pass/fail counts), and any known
behavioral differences with root cause. Future maintainers will thank you.&lt;/p&gt;
&lt;h3 id=&#34;common-migration-pitfalls&#34;&gt;Common migration pitfalls&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mixed OBJ versions&lt;/strong&gt; — Linking TP6 units with TP7-built units can produce
subtle ABI mismatches. Clean rebuild from source.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Directive inheritance&lt;/strong&gt; — Unit A’s directives can affect units that use it;
a stray &lt;code&gt;{$R-}&lt;/code&gt; in a deeply included file can disable range checks project-wide.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Overlay entry points&lt;/strong&gt; — Overlays require far calls; if &lt;code&gt;{$F-}&lt;/code&gt; is set where
overlay code is invoked, near calls hit the wrong segment and crash.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BGI driver paths&lt;/strong&gt; — TP7 may look for .BGI files in different locations;
verify InitGraph and driver loading.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FPU detection&lt;/strong&gt; — &lt;code&gt;{$N+}&lt;/code&gt; assumes FPU present; on 8086/8088, use &lt;code&gt;{$N-}&lt;/code&gt; or
runtime detection to avoid invalid opcode traps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Map file drift&lt;/strong&gt; — After migration, diff the new map against the baseline.
Segment order and symbol addresses may shift; large or unexpected changes
warrant investigation. If overlay segment names or orders change, overlay
load addresses will differ—ensure overlay manager configuration matches the
new map.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;where-this-series-goes-next&#34;&gt;Where this series goes next&lt;/h2&gt;
&lt;p&gt;You asked for practical depth, so this series now has dedicated companion labs:&lt;/p&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/turbo-pascal-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/&#34;&gt;Turbo Pascal BGI Tutorial: Dynamic Drivers, Linked Drivers, and Diagnostic Harnesses&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;full-series-index&#34;&gt;Full series index&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-1-anatomy-and-workflow/&#34;&gt;Part 1: Anatomy and Workflow&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;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-3-overlays-memory-models-and-link-strategy/&#34;&gt;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-4-graphics-drivers-bgi-and-rendering-integration/&#34;&gt;Part 4: Graphics Drivers, BGI, and Rendering Integration&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-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want the next layer, I recommend one additional article focused only on
calling-convention lab work with map-file-backed stack tracing across Pascal and
assembly boundaries.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Turbo Pascal Units as Architecture, Not Just Reuse</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-units-as-architecture/</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-units-as-architecture/</guid>
      <description>&lt;p&gt;Most people first meet Turbo Pascal units as &amp;ldquo;how to avoid copy-pasting procedures.&amp;rdquo; That is true and incomplete. In real projects, units are architecture boundaries. They define what the rest of the system is allowed to know, hide what can change, and make refactoring survivable under pressure.&lt;/p&gt;
&lt;p&gt;In constrained DOS projects, this was not academic design purity. It was the difference between shipping and debugging forever.&lt;/p&gt;
&lt;h2 id=&#34;interface-section-is-a-contract-surface&#34;&gt;Interface section is a contract surface&lt;/h2&gt;
&lt;p&gt;A good unit interface exposes minimal, stable operations. It does not leak storage details, timing internals, or helper routines with unclear ownership. You can read the interface as a capability map.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;unit RenderCore;

interface
procedure BeginFrame;
procedure DrawSprite(X, Y, Id: Integer);
procedure EndFrame;

implementation
{ internal page selection, clipping, palette handling }
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice what is missing: page indices, raw VGA register details, sprite memory layout. Those remain private so callers cannot create illegal states casually.&lt;/p&gt;
&lt;h2 id=&#34;separation-patterns-that-work&#34;&gt;Separation patterns that work&lt;/h2&gt;
&lt;p&gt;A practical retro project often benefits from explicit layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SysCfg&lt;/code&gt;: startup profile, paths, feature flags&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Input&lt;/code&gt;: keyboard state and edge detection&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RenderCore&lt;/code&gt;: page lifecycle and primitives&lt;/li&gt;
&lt;li&gt;&lt;code&gt;World&lt;/code&gt;: simulation and collision&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UiHud&lt;/code&gt;: overlays independent of camera&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each layer exports what the next layer needs, and no more.&lt;/p&gt;
&lt;p&gt;This is still modern architecture wisdom, just with smaller tools.&lt;/p&gt;
&lt;h2 id=&#34;compile-time-feedback-as-architecture-feedback&#34;&gt;Compile-time feedback as architecture feedback&lt;/h2&gt;
&lt;p&gt;One advantage of strong unit boundaries: breakage appears quickly at compile time. If you change a function signature in one interface, all dependent call sites surface immediately. That pressure encourages deliberate changes rather than implicit behavior drift.&lt;/p&gt;
&lt;p&gt;When architecture boundaries are vague, breakage tends to become runtime surprises. In DOS-era loops, compile-time certainty was a strategic advantage.&lt;/p&gt;
&lt;h2 id=&#34;state-ownership-rules&#34;&gt;State ownership rules&lt;/h2&gt;
&lt;p&gt;Global variables are tempting in small projects. They also erase accountability. Better pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;each unit owns its state&lt;/li&gt;
&lt;li&gt;mutation happens through explicit procedures&lt;/li&gt;
&lt;li&gt;read-only queries are exposed as functions&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-pascal&#34; data-lang=&#34;pascal&#34;&gt;unit FrameClock;

interface
procedure Tick;
function FrameCount: LongInt;

implementation
var
  GFrameCount: LongInt;

procedure Tick;
begin
  Inc(GFrameCount);
end;

function FrameCount: LongInt;
begin
  FrameCount := GFrameCount;
end;
end.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This small discipline scales surprisingly far.&lt;/p&gt;
&lt;h2 id=&#34;circular-dependencies-are-architecture-warnings&#34;&gt;Circular dependencies are architecture warnings&lt;/h2&gt;
&lt;p&gt;If Unit A needs Unit B and B needs A, the system is signaling a design issue. In Turbo Pascal this becomes obvious quickly because cycles are painful. Use that pain as feedback:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;extract shared abstractions into Unit C&lt;/li&gt;
&lt;li&gt;invert direction of calls through callback interfaces&lt;/li&gt;
&lt;li&gt;move policy decisions up a layer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The language/tooling friction nudges you toward cleaner dependency graphs.&lt;/p&gt;
&lt;h2 id=&#34;testing-mindset-without-modern-frameworks&#34;&gt;Testing mindset without modern frameworks&lt;/h2&gt;
&lt;p&gt;Even without a test framework, you can create deterministic validation by small harness units:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fixture setup procedure&lt;/li&gt;
&lt;li&gt;operation call&lt;/li&gt;
&lt;li&gt;assertion-like result check&lt;/li&gt;
&lt;li&gt;text output summary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is isolating seams through interfaces. If a rendering unit can be called with prepared buffers and predictable state, manual regression checks become cheap and reliable.&lt;/p&gt;
&lt;h2 id=&#34;architecture-and-performance-are-not-enemies&#34;&gt;Architecture and performance are not enemies&lt;/h2&gt;
&lt;p&gt;Some developers fear unit boundaries will cost speed. In most DOS-scale projects, the bigger performance wins come from algorithm choice and memory locality, not from collapsing all code into one monolith. Clear units help you identify hot paths accurately and optimize where it matters.&lt;/p&gt;
&lt;p&gt;For example, keeping low-level pixel paths inside &lt;code&gt;RenderCore&lt;/code&gt; makes targeted optimization straightforward while preserving clean call sites elsewhere.&lt;/p&gt;
&lt;h2 id=&#34;cross-references-in-this-project&#34;&gt;Cross references in this project&lt;/h2&gt;
&lt;p&gt;These articles show the same pattern from different angles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-1-planar-memory-model/&#34;&gt;Mode X Part 1: Planar Memory and Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/modex/modex-part-4-tilemaps-and-streaming/&#34;&gt;Mode X Part 4: Tilemaps and Streaming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/config-sys-as-architecture/&#34;&gt;CONFIG.SYS as Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/musings/the-cost-of-unclear-interfaces/&#34;&gt;The Cost of Unclear Interfaces&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Different domains, same operational truth: explicit boundaries reduce failure ambiguity.&lt;/p&gt;
&lt;h2 id=&#34;a-migration-strategy-for-messy-codebases&#34;&gt;A migration strategy for messy codebases&lt;/h2&gt;
&lt;p&gt;If you already have a tangled Pascal codebase, do not rewrite everything. Do staged extraction:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;identify one unstable subsystem&lt;/li&gt;
&lt;li&gt;define minimal interface for it&lt;/li&gt;
&lt;li&gt;move internals behind unit boundary&lt;/li&gt;
&lt;li&gt;replace direct global access with explicit calls&lt;/li&gt;
&lt;li&gt;repeat for next subsystem&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach keeps software running while architecture improves incrementally.&lt;/p&gt;
&lt;p&gt;Turbo Pascal units are sometimes framed as nostalgic language features. They are better understood as practical architecture tools with excellent signal-to-noise ratio. Under constraints, that ratio is everything.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Writing Turbo Pascal in 2025</title>
      <link>https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-in-2025/</link>
      <pubDate>Sun, 19 Oct 2025 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-in-2025/</guid>
      <description>&lt;p&gt;Turbo Pascal 7.0 still compiles in under a second on a 486. On DOSBox-X
running on modern hardware, it&amp;rsquo;s instantaneous. The IDE — blue background,
yellow text, pull-down menus — is the direct ancestor of the Turbo Vision
library that inspired this site&amp;rsquo;s theme.&lt;/p&gt;
&lt;p&gt;I wrote a small unit that reads the RTC via INT 1Ah and formats it as
ISO 8601. The entire program, compiled, is 3,248 bytes. Try getting that
from a modern toolchain.&lt;/p&gt;
&lt;p&gt;What surprised me was not just speed, but focus. Turbo Pascal&amp;rsquo;s workflow is
so tight that experimentation becomes natural: edit, compile, run, inspect,
repeat. No dependency resolver, no plugin lifecycle, no hidden build graph.
You can reason about the whole stack while staying in flow.&lt;/p&gt;
&lt;h2 id=&#34;why-it-is-still-worth-touching&#34;&gt;Why it is still worth touching&lt;/h2&gt;
&lt;p&gt;Turbo Pascal remains one of the best environments for learning low-level
software discipline without drowning in tooling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;strong typing with low ceremony&lt;/li&gt;
&lt;li&gt;explicit artifacts (&lt;code&gt;.PAS&lt;/code&gt;, &lt;code&gt;.TPU&lt;/code&gt;, &lt;code&gt;.OBJ&lt;/code&gt;, &lt;code&gt;.EXE&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;immediate compile-run feedback&lt;/li&gt;
&lt;li&gt;clear memory and ABI consequences&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to sharpen systems instincts, this is still high-return practice.&lt;/p&gt;
&lt;h2 id=&#34;practical-2025-setup-that-stays-reproducible&#34;&gt;Practical 2025 setup that stays reproducible&lt;/h2&gt;
&lt;p&gt;My baseline:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;pin one DOSBox-X config per project&lt;/li&gt;
&lt;li&gt;mount a host directory as project root&lt;/li&gt;
&lt;li&gt;keep &lt;code&gt;BUILD.BAT&lt;/code&gt; for CLI parity with IDE actions&lt;/li&gt;
&lt;li&gt;version notes + build profile options in plain text&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Expected outcome:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;same source builds the same way after a long break&lt;/li&gt;
&lt;li&gt;less dependence on undocumented IDE state&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;what-to-practice-first-30-90-minute-labs&#34;&gt;What to practice first (30-90 minute labs)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Build a two-unit app and observe incremental rebuild behavior.&lt;/li&gt;
&lt;li&gt;Link one external &lt;code&gt;.OBJ&lt;/code&gt; routine and verify ABI correctness.&lt;/li&gt;
&lt;li&gt;Enable one overlayed cold path and measure first-hit latency.&lt;/li&gt;
&lt;li&gt;Initialize BGI with diagnostic harness and test broken path behavior.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These labs map directly to the deeper series below.&lt;/p&gt;
&lt;h2 id=&#34;read-this-as-a-progression&#34;&gt;Read this as a progression&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/toolchain/turbo-pascal-toolchain-part-1-anatomy-and-workflow/&#34;&gt;Turbo Pascal Toolchain, Part 1: Anatomy and Workflow&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;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-3-overlays-memory-models-and-link-strategy/&#34;&gt;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-4-graphics-drivers-bgi-and-rendering-integration/&#34;&gt;Part 4: Graphics Drivers, BGI, and Rendering Integration&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-5-from-6-to-7-compiler-linker-and-language-growth/&#34;&gt;Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth&lt;/a&gt;&lt;/li&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;Overlay Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-bgi-tutorial-dynamic-drivers-linked-drivers-and-diagnostic-harnesses/&#34;&gt;BGI Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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/batch-file-wizardry/&#34;&gt;Batch File Wizardry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/hardware/restoring-a-286/&#34;&gt;Restoring an AT 286&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Batch File Wizardry</title>
      <link>https://turbovision.in6-addr.net/retro/dos/batch-file-wizardry/</link>
      <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate>
      <lastBuildDate>Mon, 09 Mar 2026 09:46:27 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/dos/batch-file-wizardry/</guid>
      <description>&lt;p&gt;DOS batch files have no arrays, no functions, and barely have variables.
Yet people built menu systems, BBS doors, and even games with them.&lt;/p&gt;
&lt;p&gt;The trick is &lt;code&gt;GOTO&lt;/code&gt; and &lt;code&gt;CHOICE&lt;/code&gt; (or &lt;code&gt;ERRORLEVEL&lt;/code&gt; parsing on older DOS).
Combined with &lt;code&gt;FOR&lt;/code&gt; loops and environment variable manipulation, you can
create surprisingly interactive scripts. We build a file manager menu
in pure &lt;code&gt;.BAT&lt;/code&gt; that would feel at home on a 1992 shareware disk.&lt;/p&gt;
&lt;p&gt;The charm of batch scripting is that constraints are obvious. You cannot hide
behind abstractions, so control flow has to be explicit and disciplined. A
good &lt;code&gt;.BAT&lt;/code&gt; file reads like a state machine: menu, branch, execute, return.&lt;/p&gt;
&lt;h2 id=&#34;patterns-that-still-hold-up&#34;&gt;Patterns that still hold up&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use descending &lt;code&gt;IF ERRORLEVEL&lt;/code&gt; checks after &lt;code&gt;CHOICE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Isolate repeated screen/header logic into callable labels.&lt;/li&gt;
&lt;li&gt;Validate file paths before launching external tools.&lt;/li&gt;
&lt;li&gt;Keep environment variable scope small and predictable.&lt;/li&gt;
&lt;li&gt;Always provide a safe &amp;ldquo;return to menu&amp;rdquo; path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These rules prevent the classic batch failure mode: jumping into a dead label
or leaving the user in an unexpected directory after an error.&lt;/p&gt;
&lt;h2 id=&#34;building-a-useful-menu-shell&#34;&gt;Building a useful menu shell&lt;/h2&gt;
&lt;p&gt;A practical structure is a top menu plus focused submenus (&lt;code&gt;UTIL&lt;/code&gt;, &lt;code&gt;DEV&lt;/code&gt;,
&lt;code&gt;GAMES&lt;/code&gt;, &lt;code&gt;NET&lt;/code&gt;). Each action should print what it is about to run, execute,
and then pause on failure. That tiny bit of observability saves debugging
time when scripts grow beyond toy examples.&lt;/p&gt;
&lt;p&gt;Batch is primitive, but that is exactly why it teaches sequencing, error
handling, and operator empathy so well.&lt;/p&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/turbo-pascal-in-2025/&#34;&gt;Writing Turbo Pascal in 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/c-after-midnight-a-dos-chronicle/&#34;&gt;C:\ After Midnight: A DOS Chronicle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
