Turbo Pascal Toolchain, Part 4: Graphics Drivers, BGI, and Rendering Integration

C:\RETRO\DOS\TP\TOOLCH~1>type turbop~4.htm

Turbo Pascal Toolchain, Part 4: Graphics Drivers, BGI, and Rendering Integration

Turbo Pascal graphics was never just “call Graph and draw.” 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.

This part focuses on BGI driver mechanics, practical packaging, and the exact checks that separate real faults from folklore.

Structure map: BGI architecture and operational models → Graph unit runtime contracts and GraphResult 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.

BGI architecture in practical terms

The Graph 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.

  • driver assets: usually .BGI (e.g. EGAVGA.BGI, CGA.BGI)
  • font assets: .CHR stroked fonts (e.g. TRIP.CHR, GOTH.CHR)
  • initialization: InitGraph(driver, mode, path)
  • status reporting: GraphResult
  • cleanup: CloseGraph

Two operational models exist:

  1. Dynamic runtime loading from filesystem path — driver and font files are read from disk at InitGraph time.
  2. Linked-driver — driver (and optionally font) binaries converted to .OBJ and linked into the executable; registration APIs make them available before InitGraph.

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.

Graph unit runtime contracts and GraphResult handling

Every Graph operation that can fail updates an internal error code. GraphResult returns that code and, in TP5, resets it to zero on read. That one-read semantic causes subtle bugs when code checks GraphResult multiple times or assumes it remains set across calls.

Contract rules:

  1. Call GraphResult once after any operation that may fail, store the value in a local variable, then branch on that variable.
  2. Do not assume GraphResult stays non-zero until the next failed operation.
  3. Never call GraphResult before the operation you intend to check — earlier successful operations clear it.
{ WRONG: second check sees zero from first read }
InitGraph(gd, gm, '.\BGI');
if GraphResult <> grOk then Halt(1);
if GraphResult <> grOk then ...   { always passes; result was cleared }

{ RIGHT: single read, then use stored value }
InitGraph(gd, gm, '.\BGI');
gr := GraphResult;
if gr <> grOk then
  begin
    Writeln('Init failed: ', gr);
    Halt(1);
  end;

TP5 error codes worth memorizing for triage:

Code Constant Typical cause
0 grOk Success
-1 grNoInitGraph Graphics not initialized
-2 grNotDetected No compatible adapter found
-3 grFileNotFound Driver or font file missing on path
-4 grInvalidDriver Driver format invalid or mismatched
-5 grNoLoadMem Not enough heap for driver/buffer
-8 grFontNotFound Font file missing
-9 grNoFontMem Not enough heap for font
-10 grInvalidMode Mode not supported by driver
-11 grError General error; often registration/order violation

When grNoLoadMem appears, suspect overlay buffer sizing or TSR load order before blaming hardware. When grFileNotFound appears, verify PathToDriver resolves from the process’s current directory, not the source tree. Some TP/BP variants may use PathStr 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.

Uncertainty note: Exact GraphResult 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’s GRAPH.TPU or include files.

TP5 baseline facts from the reference guide

For Turbo Pascal 5.0, the reference guide is explicit:

  • compile-time dependency: GRAPH.TPU
  • runtime dependency: one or more .BGI drivers
  • if stroked fonts are used: one or more .CHR files

InitGraph loads the selected driver and enters graphics mode. CloseGraph unloads/restores previous mode. This is the lifecycle baseline. After CloseGraph, you may re-enter graphics mode with another InitGraph call, but driver and font state are reset; any registered user drivers must be re-registered if you use the linked model.

Dynamic model: fastest to start, easiest to break in deployment

uses Graph;
var
  gd, gm, gr: Integer;
begin
  gd := Detect;
  InitGraph(gd, gm, 'C:\APP\BGI');
  gr := GraphResult;
  if gr <> grOk then
    begin
      Writeln('BGI init failed: ', gr);
      Halt(1);
    end;
  { render }
  CloseGraph;
end.

Expected outcome:

  • works immediately in dev environment with full BGI directory
  • fails fast if path/assets are missing, with actionable error code

Common failure is not code. It is wrong path assumptions after installation. Typical mistakes: hardcoding C:\TP\BGI or .\BGI when the app runs from A:\ or a network drive; assuming GetDir equals executable directory; using forward slashes on systems that expect backslashes.

TP5 path behavior: if PathToDriver 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 ParamStr(0) or GetDir, and ensure it ends with \.

Path resolution example:

uses Dos, Graph;
var
  ExeDir, BgiPath: PathStr;
  Name, Ext: PathStr;
begin
  FSplit(ParamStr(0), ExeDir, Name, Ext);
  BgiPath := ExeDir + 'BGI' + '\';
  InitGraph(gd, gm, BgiPath);
  gr := GraphResult;
  ...
end.

This assumes BGI is a subdirectory next to the executable. If you ship with BGI alongside .EXE, this pattern works regardless of where the user installed the app.

Triage for dynamic-load failures:

  1. Run the diagnostic harness (see below) from the same directory and path the app will use in production.
  2. If harness works but app fails, compare paths and current-directory assumptions between harness and app.
  3. If grFileNotFound: list directory contents, verify file names match exactly (case may matter on some setups).
  4. If grNoLoadMem: reduce overlay buffer, close TSRs, or switch to linked driver.

Linked-driver model: more robust runtime, tighter build coupling

Some Borland-era toolchains support converting/linking driver binaries into object form and registering them at startup (for example via RegisterBGIdriver and companion font registration APIs). This avoids runtime dependency on external .BGI files but increases binary size and build complexity.

Practical pattern:

  1. convert/select driver object module
  2. link object into project ({$L ...} or linker config)
  3. register driver before InitGraph
  4. call InitGraph with empty or local path expectations

Exact symbol names and conversion utilities depend on installation/profile, so document your specific toolchain once and keep it version-pinned.

TP5 manual flow for linked drivers is concrete:

  1. convert .BGI to .OBJ with BINOBJ
  2. link resulting .OBJ into the executable
  3. call RegisterBGIdriver before InitGraph

If you call RegisterBGIdriver after graphics are already active, TP5 reports grError (-11). Same rule applies to RegisterBGIfont: register before first use of that font.

BINOBJ invocation (exact syntax varies by Borland install):

1
BINOBJ EGAVGA.BGI EGAVGA EGAVGA

This produces EGAVGA.OBJ with symbols for the binary blob. The linker then pulls it in via {$L EGAVGA.OBJ}. 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 .OBJ to your build and ensure it is linked before the unit that calls RegisterBGIdriver. If the symbol is undefined at link time, the .OBJ was not included or the declaration does not match BINOBJ output.

Illustrative registration shape (symbol names vary by conversion/tooling):

{$L EGAVGA.OBJ}

procedure RegisterEgaVga; external;

begin
  RegisterBGIdriver(@RegisterEgaVga);
  { or InstallUserDriver + callback, depending on toolchain }
  InitGraph(gd, gm, '');
  gr := GraphResult;
  if gr <> grOk then Halt(1);
  { ... }
end.

Treat symbol names as toolchain-specific; BINOBJ output and TP/BP docs define the exact entry. If registration order is wrong, you get grError with no obvious message — add logging before each Graph call during integration.

Pitfalls: Forgetting to register before InitGraph; registering after InitGraph; linking the wrong driver .OBJ for the target adapter; mixing driver versions (e.g. TP5 vs BP7) when BGI format differs. Another pitfall: assuming Detect returns the same driver on all VGA systems. Some VGA clones or BIOS quirks can cause Detect to fail or return a conservative mode; hardcoding a fallback (e.g. if gd = Detect then gd := VGA; gm := VGAHi) can improve robustness when autodetect is unreliable. When linking multiple drivers (e.g. VGA + CGA fallback), register all before InitGraph; 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 .OBJ 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.

Asset set discipline (driver + font matrix)

For each shipping mode profile, define and freeze:

  • required driver files (e.g. EGAVGA.BGI for VGA, CGA.BGI for CGA fallback)
  • required font files (e.g. TRIP.CHR, GOTH.CHR if SetTextStyle uses them)
  • fallback mode behavior (what mode to try if Detect fails or preferred mode unavailable)
  • startup diagnostics text (what to print on failure)

Without this matrix, BGI deployment drifts silently between machines. One developer ships with EGAVGA.BGI only; another’s machine has CGA.BGI in path; field reports “black screen” and nobody knows which adapter or driver set was used.

Driver and font packaging rules: Drivers and fonts must be version-pinned to the toolchain that produced GRAPH.TPU. TP5 EGAVGA.BGI is not guaranteed compatible with BP7’s Graph unit; format and entry-point layout can differ. Package drivers as a locked set: document “EGAVGA.BGI from TP5.0 install dated 1989” in your release notes. Fonts are similarly sensitive: a .CHR 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.

Font/driver matrix discipline: Not all fonts work with all drivers. Stroked fonts (.CHR) are driver-independent in principle, but SetTextStyle calls before a font is loaded fall back to default. Document which fonts are required for each UI path. If you use InstallUserFont or RegisterBGIfont, the registration order and timing must match the matrix — register before any SetTextStyle that selects that font. A minimal matrix might look like:

Target adapter Driver Fonts used Fallback mode
VGA EGAVGA.BGI TRIP, GOTH VGAHi
EGA EGAVGA.BGI TRIP EGALo
CGA CGA.BGI (default) CGAC0

Ship only drivers and fonts listed for your supported targets. Including extra files “just in case” 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.

BGI artifacts in build and deploy pipelines

BGI assets are build outputs as much as runtime dependencies. Include them in your artifact pipeline so releases are reproducible.

Package layout:

1
2
3
4
5
6
7
8
9
RELEASE/
  MYAPP.EXE
  BGI/
    EGAVGA.BGI
    CGA.BGI
  FONTS/
    TRIP.CHR
    GOTH.CHR
  README.TXT

If using linked drivers, BGI/ and FONTS/ may be empty, but the layout should still be documented so installers know what to expect.

Build script integration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@echo off
set BGI_SRC=C:\TP\BGI
set BGI_OUT=..\RELEASE\BGI
if not exist %BGI_OUT% mkdir %BGI_OUT%
copy %BGI_SRC%\EGAVGA.BGI %BGI_OUT%\
copy %BGI_SRC%\CGA.BGI %BGI_OUT%\
if not exist ..\RELEASE\FONTS mkdir ..\RELEASE\FONTS
copy %BGI_SRC%\TRIP.CHR ..\RELEASE\FONTS\
rem checksum for release manifest
... checksum tool ...

Run the diagnostic harness as a post-build step: execute it from RELEASE\ with BGI as subdirectory and assert GraphResult = grOk. If the harness fails in clean build output, fix paths before shipping. Some teams wired the harness as BUILD.BAT final step with if errorlevel 1 to fail the build.

Checksum discipline: Store MD5 or CRC of each .BGI and .CHR in a manifest. When field reports “weird corruption” or mode errors, compare checksums to rule out truncated or swapped files. A swapped CGA.BGI and EGAVGA.BGI (e.g. misnamed copies) produces grInvalidDriver or garbled output; checksums catch that quickly. Run checksum verification as part of the build pipeline: after copying assets to RELEASE\, compute checksums and append to a MANIFEST.TXT; 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.

Floppy and installer considerations: If distributing on floppies, put MYAPP.EXE on disk 1 and BGI\ contents on the same or next disk. Installer scripts should copy BGI\ 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 MYAPP from C:\GAMES\ and expect .\BGI to mean C:\GAMES\BGI.

Are BGI file formats fully documented?

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.

What you can reliably do today:

  • verify driver/font assets exist and match expected set
  • checksum assets as release artifacts
  • use diagnostic harnesses to confirm runtime load path

Diagnostics pitfall: Do not assume a BGI file is valid just because it exists. A truncated or corrupted file can produce grInvalidDriver or unpredictable behavior. If you suspect file integrity, compare size and checksum against a known-good copy from your toolchain installation.

“How are BGI drivers created?” practical answer

Three realistic paths:

  1. Use stock Borland drivers (most common historical path). Ship EGAVGA.BGI, CGA.BGI, 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 GRAPH.TPU.
  2. Link stock drivers into executable for deployment robustness. Convert with BINOBJ, link, register. Same version-pinning rule applies.
  3. Author custom driver 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.

Path 3 is advanced reverse-engineering/ABI work. It is possible, but not the right default for project delivery unless driver capabilities are missing.

BGI startup diagnostics harness (must-have)

program BgiDiag;
uses Graph, Crt;
var
  gd, gm, gr: Integer;
begin
  gd := Detect;
  InitGraph(gd, gm, '.\BGI');
  gr := GraphResult;
  Writeln('Driver=', gd, ' Mode=', gm, ' GraphResult=', gr);
  if gr = grOk then
  begin
    SetColor(15);
    OutTextXY(8, 8, 'BGI init OK');
    Line(0, 0, GetMaxX, GetMaxY);
    ReadKey;
    CloseGraph;
  end
  else
    Writeln('Init failed. Check path, files, memory.');
end.

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 grFontNotFound appears in the field, or a Detect-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 BGIDIAG.EXE in the release package so support could ask users to run it and report the printed codes — a single number (GraphResult) often suffices to distinguish path, memory, and driver issues without requiring logs or repro steps.

Triage procedure with the harness:

  1. Run from project root with .\BGI containing drivers — expect grOk.
  2. Run from same directory but rename BGI to BGI_BACKUP — expect grFileNotFound; confirm printed code matches.
  3. Run from a directory without BGI subfolder — expect grFileNotFound.
  4. On a TSR-heavy config (mouse, network driver, etc.), run harness — if grNoLoadMem, document minimum free conventional memory for your build.

Important TP5 behavior: GraphResult resets to zero after it has been called. Store it in a variable once and evaluate that variable.

Font handling is a real subsystem

If UI layout depends on font metrics, .CHR assets are first-class artifacts:

  • version and checksum them
  • package with same discipline as executables
  • test fallback behavior explicitly

Silent fallback to default font can break coordinates, clipping, and hit zones. A menu rendered with GOTH.CHR has different line heights than the default; if the font fails to load, text may overlap or clip incorrectly.

TP5 adds two separate extension points:

  • InstallUserFont (register by name/file path)
  • RegisterBGIfont (register loaded or linked-in font pointer)

As with drivers, registration must be done before normal graphics workflow relies on those resources. After SetTextStyle(...), check GraphResult if the font was user-installed; grFontNotFound or grNoFontMem indicate load failure.

Memory interaction: Stroked fonts are loaded into heap. A large .CHR plus graphics buffer plus overlay buffer can exhaust conventional memory. If grNoFontMem 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 .CHR from the TP install “for completeness” — this bloats the package and increases the chance of loading the wrong font by typo or path confusion. If SetTextStyle 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 SetTextStyle for user fonts and log GraphResult during development.

BGI + overlays + memory budget interaction

Graphics initialization and overlays interact with memory pressure. If startup becomes unstable after enabling overlays or TSR-heavy profiles, validate:

  1. available memory headroom before InitGraph
  2. overlay manager buffer size (OvrSetBuf)
  3. order of subsystem initialization

Treat graphics bugs and memory bugs as potentially coupled until proven otherwise. Memory interplay with overlays is the most common source of “works in dev, fails in release” 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. grNoLoadMem often appears when overlay and Graph compete for the same pool without a clear initialization order.

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 (OvrSetBuf) and InitGraph order can conflict.

Order rule: Call OvrSetBuf (and OvrInit) before InitGraph. 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: InitGraph succeeds, then OvrSetBuf shrinks the heap, and a later overlay load or Graph operation fails with grNoLoadMem or overlay error. The fix is to establish overlay buffer first, then let Graph allocate from the remaining free memory.

OvrInit(OvrFile);
if OvrResult <> ovrOk then Halt(1);
OvrSetBuf(50000);     { before InitGraph }
InitGraph(gd, gm, '.\BGI');
gr := GraphResult;

Memory budgeting: 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. “400 KB free conventional memory”) 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 “works on my machine” deployment failures.

Overlay–Graph allocation interplay: The heap layout after OvrSetBuf and InitGraph 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 (“Free mem: XXXXX”) before InitGraph to catch regressions. Uncertainty note: Exact allocation order and sizes can vary between TP5, TP6, and BP7; when in doubt, instrument and measure on your target configuration.

Debugging rendering failures on real DOS systems

When graphics fail in the field but work in development, systematic triage narrows the cause. Use a rendering triage loop: 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. “Line draws wrong”) that stem from an earlier init or mode problem.

Release verification on real hardware: 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 A:\, C:\GAMES, 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 < 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.

Triage steps:

  1. Black screen, no message: Program may be exiting before any output. Add a Writeln('Starting...') before InitGraph; if it never appears, the crash is earlier (e.g. overlay init, missing .OVR). On some DOS configurations, mode switch can also blank the screen before text output is visible; redirect output to a file (MYAPP > LOG.TXT 2>&1) to confirm whether any text was produced.
  2. Black screen, message appeared then vanished: Mode switch may have failed, or the program exited immediately. Ensure GraphResult is checked and stored before any cleanup; add ReadKey or Delay before CloseGraph in harness to confirm. If the message flashes too quickly to read, write it to a file as well.
  3. Wrong resolution or garbled display: Driver/mode mismatch. Detect may pick a different mode on different adapters; log gd and gm and compare to adapter documentation. Force a known mode (e.g. gd := VGA; gm := VGAHi) for compatibility testing.
  4. Works once, fails on second run: TSR or environment pollution. Reboot to clean state; disable TSRs one by one. Some drivers leave video state inconsistent after CloseGraph.
  5. grNoLoadMem on target only: Conventional memory too low. Run MEM before app; compare to dev machine. Reduce overlay buffer or ship linked driver build.

Keep a triage log: adapter type, driver set, free memory, TSR list, and exact GraphResult value. Reproducible cases go into the test matrix. When a new symptom appears (e.g. “screen flickers then goes black”), 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.

Team checklists and release hardening

Before release, the team should complete:

Pre-build:

  • Unit search path, object path, and BGI asset path documented and version-pinned
  • Build script produces deterministic output (clean build, no stale artifacts)

Build:

  • All required .BGI and .CHR copied to release layout
  • Diagnostic harness runs successfully from release directory
  • Checksums recorded for .EXE, .OVR (if used), .BGI, .CHR

Post-build verification:

  • Test from directory different from source (e.g. C:\TEST\, A:\)
  • Intentionally remove one driver, run app — verify error message, no crash
  • Test with overlay file missing (if applicable) — verify controlled exit
  • One memory-stressed run (e.g. with MEM reporting < 400 KB free)
  • Run diagnostic harness from same release layout; assert it reports grOk
  • If distributing on floppy: boot from disk 1, run from A:\, confirm BGI path resolves correctly (e.g. A:\BGI\ when app is on A:\)

Release package:

  • README includes BGI path requirements and minimum memory
  • Build manifest (checksums, compiler version, options) archived with artifacts

Expected outcome: actionable startup message on any failure, never black-screen ambiguity. When a user reports “does not work,” 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 “black screen” with no data. A single Writeln('BGI error: ', gr) before Halt(1) can save days of debugging.

Useful TP5 InitGraph failure codes to log:

  • grNotDetected (-2)
  • grFileNotFound (-3)
  • grInvalidDriver (-4)
  • grNoLoadMem (-5)
  • grInvalidMode (-10)

Where to go deeper

Next:

2026-02-22 | MOD 2026-03-09