<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Graphics on TurboVision</title>
    <link>https://turbovision.in6-addr.net/tags/graphics/</link>
    <description>Recent content in Graphics 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/graphics/index.xml" rel="self" type="application/rss&#43;xml" />
    
    
    
    <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 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 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>
    
  </channel>
</rss>
