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