Mode X in Turbo Pascal, Part 3: Sprites and Palette Cycling
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.
If your primitives and clipping are not stable yet, go back to Part 2. Sprite bugs are hard enough without foundational uncertainty.
Sprite data strategy: keep it explicit
A reliable sprite pipeline separates three concerns:
- Source pixel data.
- Optional transparency mask.
- Draw routine that respects clipping and planes.
Trying to “infer” transparency from arbitrary colors in ad-hoc code works until assets evolve. Use explicit conventions and document them in your asset converter notes.
Masked blit pattern
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.
Pseudo-shape:
for sy := 0 to SpriteH - 1 do
for sx := 0 to SpriteW - 1 do
if Mask[sx, sy] <> 0 then
PutPixelX(DstX + sx, DstY + sy, Sprite[sx, sy]);
You can optimize later with span-based opaque runs. First make it correct under clipping and page boundaries.
Clipping sprites without branching chaos
A practical trick: precompute clipped source and destination windows once per sprite draw call. Then inner loops run branch-light:
srcStartX/srcStartYsrcEndX/srcEndYdstStartX/dstStartY
This keeps the “should I draw this pixel?” decision out of every iteration and dramatically reduces bug surface.
Draw order as policy
In old-school 2D engines, z-order usually means “draw in sorted sequence.” Keep that sequence explicit:
- background
- terrain decals
- actors
- projectiles
- effects
- HUD
When overlap glitches appear, deterministic order lets you debug with confidence instead of guessing whether timing or memory corruption is involved.
Palette cycling: cheap motion, strong mood
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 “animate” automatically. Water shimmer, terminal glow, warning lights, and magic effects become nearly free per frame.
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;
The artistic rule is simple: reserve palette bands intentionally. If artists and programmers share the same palette map vocabulary, effects stay predictable.
Timing: lock behavior before optimization
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:
- fixed simulation tick (e.g., 70 Hz or 60 Hz equivalent)
- render as often as practical
- interpolate only when necessary
Even on retro hardware, disciplined timing produces smoother perceived motion than occasional fast spikes.
Debug overlays save projects
Add optional overlays you can toggle with a key:
- sprite bounding boxes
- clip rectangles
- page index
- tick/frame counters
- palette band IDs
These overlays are not “debug clutter.” They are observability for graphics systems that otherwise fail visually without explanation.
Cross references that help this stage
Each one contributes a different layer: memory model, primitive discipline, and workflow habits.
Next article
Part 4 moves to tilemaps, camera movement, and data streaming from disk into playable scenes:
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.
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.