Skip to content
The CNA BibleCNA 0.1.0-alpha.1 Edition

Chapter 75 Case Study: Speedy Blupi and Planet Blupi

Two legacy Windows games shaped free-direct’s scope: Speedy Blupi (through the sibling free-eggbert repository) and Planet Blupi. Both run through the compatibility layer. A separate native-CNA feasibility study remains a proposal. The distinction lets this chapter compare an implemented migration bridge with a source-audited port plan.

75.1 Call-site-driven scope

free-direct’s subsystem boundaries come from audits of the two games at named commits. Both use DirectDraw and DirectSound; only Free Eggbert uses DirectPlay. free-eggbert calls Blt four times and BltFast six times; Planet Blupi calls them three and five times. In each game, one Blt performs the once-per-frame back-buffer-to-primary present. This call-site pressure explains why Chapter 60 describes DirectDraw as the most developed of the library’s three subsystems.

75.1.1 Raw occurrences versus interface calls

A later recount reports eighteen BltFast occurrences in free-eggbert and seventeen in Planet Blupi. That population includes header declarations, definitions of each game’s CPixmap::BltFast wrapper, and calls to both the wrapper and the DirectDraw interface. The six and five counts include only calls that reach IDirectDrawSurface::BltFast, the boundary implemented by free-direct. The other eight calls per game enter the local wrapper, which then makes one of those interface calls. Counting both levels would count the same traffic twice. Call-site totals therefore need an explicit population definition.

75.2 A pixel-format mismatch that never became a visible bug

Both games request an explicit display depth: free-eggbert selects 8 or 16 bits through a runtime flag, while Planet Blupi requests 8 bits. free-direct’s SetDisplayMode ignores that value and creates a 32-bit primary surface. The deviation does not affect these two games because their offscreen surfaces follow a separate, hard-coded 32-bit path. Every observed producer and consumer therefore agrees on the delivered format. This is a verified harmless deviation for the audited callers, not a general guarantee about ignoring pixel-format requests.

75.3 Color keying: implementation broader than observed use

Planet Blupi uses source color-keying at two call sites through one caching helper. Both construct a single-value range and match white through a GetDC / SetPixel / Lock round trip, not through a fixed palette index. free-direct implements low-to-high ranges, but the audited games exercise only the single-value case. A third game would need separate evidence for ranged behavior.

75.4 GetDC as a gameplay dependency

Planet Blupi’s IsIconPixel performs a per-click gameplay hit test through GetDC / ReleaseDC. The implementation must handle both a zero-copy 32-bit wrap and an 8-bit surface expanded into a temporary palette-converted buffer. An API that appears to be incidental GDI interoperation therefore sits on an active gameplay path.

75.5 A performance audit adds exact costs to the same call sites

A later performance audit benchmarked the compiled library headlessly with SDL_VIDEODRIVER=dummy. DirectDraw 3’s BltFast has no destination-size parameter, so it cannot scale. Every audited BltFast interface call in both games (free-eggbert: pixmap.cpp:406,574,580,612,1762,1818; Planet Blupi: pixmap.cpp:395,401,433,1235,1291) is consequently 1:1. At the time of measurement, the shared blit engine still performed a per-pixel integer division and bit-depth branch for these copies. It measured 29 times slower than a raw memcpy of the identical byte count at an optimized build (0.715 ms versus 0.0244 ms for one 640×480 32-bit blit), and 206 times slower under the then-default unoptimized configuration.

Build BltFast memcpy Ratio
Default (no -O flags) 4.704 ms 0.0229 ms 205.8×
-DCMAKE_BUILD_TYPE=Release 0.715 ms 0.0244 ms 29.4×

Those numbers are discovery evidence, not the current implementation. The pin now defaults an otherwise-unspecified top-level, single-configuration free-direct build to Release while leaving a consuming project’s choice untouched. It also detects equal source/destination extents at runtime and performs per-row memcpy when format and color-key conditions permit, followed by the required opaque-alpha fixup. The recorded follow-up benchmark reduced the same Release case from 0.715 ms to 0.466 ms, or from 29.4 to 14.8 times the raw-copy reference. The remaining gap is mostly the alpha pass; the original 205.8-times row no longer describes a plain standalone configure. The two measurements preserve the before-and-after evidence instead of presenting the discovery result as current performance.

One Blt-family call does require scaling: free-eggbert’s CPixmap::Display() (pixmap.cpp:1509--1516) presents the fixed internal resolution into the window client area. The 1:1 fast path therefore compares the two rectangles at runtime. The audit also found dead code on the game side: free-eggbert’s CPixmap::DrawMap(int, RECT, RECT) (pixmap.cpp:640) has exactly one Blt() call, but no callers of its three-argument signature in the inspected source.

75.6 Interfaces never exercised at all

Across both games, the only QueryInterface call belongs to a DirectPlay object. None targets the four DirectDraw classes, whose free-direct implementations return DDERR_UNSUPPORTED. Planet Blupi contains no DirectPlay use, so the DirectPlay scope described in Chapter 60 comes entirely from free-eggbert.

75.7 DirectSound structure-layout hazard

Both games populate PCMWAVEFORMAT, not the larger WAVEFORMATEX. The layouts place wBitsPerSample at different offsets; interpreting the caller’s pointer as WAVEFORMATEX would read padding as the sample depth. free-direct therefore stores a raw void* and interprets these audited calls as PCMWAVEFORMAT. Both games also call SetPan() only on mono effects. That explains why the intended contract was documented as mono-only, but it does not make current panning audible. The implementation computes left/right gains and discards them; only the ordinary volume gain reaches SDL. The tests prove clamping, return values, and no-crash behavior, not an audible stereo split.

75.8 Dead and unexercised paths

free-eggbert’s alternate BASS-backed sound implementation is behind a compile-time flag fixed to false with no build override. Each game also contains a wave-loading source file that only includes itself. Finally, every inspected call to the sound-playback method passes zero for the looping flags. This caller-side result agrees with Chapter 60’s implementation-side conclusion that the two games cannot reach DirectSound looping.

Two narrower searches bounded possible additions. Excluding vendored DirectX 3 SDK headers, neither game calls GetCurrentPosition; free-direct declares only its SetCurrentPosition counterpart. The method remains unimplemented until a target caller requires it. Neither game explicitly requests an 8-bit CreateSurface format either. Together with the 32-bit primary path in §75.2, this means the audited games do not exercise BlitFrom’s silent mixed-depth fallthrough. That behavior remains a limitation, even though adding a new error route was outside the target-driven scope.

75.9 Native-CNA port feasibility

The preceding evidence concerns the free-direct compatibility shell. A later cna.md in the inspected free-eggbert working tree, dated 2026-07-16, studies a different question: what would it take to port Free Eggbert directly onto CNA’s Game / GraphicsDevice / SpriteBatch surface, replacing free-direct (and free-api beneath it) rather than merely running through it? That 30,145-byte document was untracked, so it is supplemental evidence outside Appendix  D’s immutable Git pins; the exact inspected copy has SHA-256 87b1e3de041cdbe2af85c8c4b4faf6e74f0e518f9ff72f851f339e5419ceadb3. It reframes the compatibility layer as a migration bridge, not a migration destination — a distinction the analysis itself states plainly: “a CNA application using the FREEDIRECT renderer has adopted the XNA-facing API, but it still transitively depends on free-direct.”

75.9.1 Scope from a direct line count

Free Eggbert is already C++; the port does not require a language translation. The analysis measures roughly 31,800 lines of C++ source and headers (a later recount in the same document gives 31,773 across src/*.cpp and include/*.hpp), with more than 21,000 of those lines — the majority — in simulation, world, object, and event logic. It therefore recommends rewriting the platform-services layer (graphics, input, audio, video, files, networking) while leaving the existing simulation/data layer (decblupi, decmove, decblock, decdesign, decio, and the game’s tables) recognizable. Preserving that layer also preserves a direct comparison point for old and new behavior.

75.9.2 Preserving the CPixmap boundary

The analysis recommends retaining CPixmap’s public shape across its 98 direct call sites and replacing only its implementation, with Texture2D / RenderTarget2D / SpriteBatch objects standing in for DirectDraw surfaces. This separates rendering equivalence from the risk of editing 98 call sites. SetTransparent2’s color-key-range semantics are singled out as the one piece of that surface CNA’s current .cnj metadata cannot express directly (it represents a single exact RGB value, not an inclusive range), so a Free Eggbert-specific loader would need to read pixels and zero alpha across the requested range by hand via GetData / SetData — described as “straightforward” precisely because the asset sheets involved are small, not because the technique is trivial in general.

75.9.3 Preserving the 50ms simulation cadence

Free Eggbert updates from a 50ms multimedia timer, a fixed 20Hz simulation cadence. CNA’s Game defaults to roughly 16.67ms (60Hz). Without an explicit TargetElapsedTime, the port would advance the simulation about three times too fast; see Chapter 6 for the timing contract. The existing F5–F8 speed controls add another constraint. The analysis recommends representing them as multiple fixed 50ms simulation steps per rendered frame (2/4/8 steps) instead of shortening TargetElapsedTime, preserving fixed-size simulation steps.

75.9.4 MIDI integration gap

The ten inspected music files are Standard MIDI data, and CNA’s own Song loader does not register .mid. The feasibility analysis correctly identifies that public integration gap, but its explanation predates a fuller decoder audit. CNA disables FluidSynth, yet its vendored SDL3_mixer build leaves the Timidity MIDI decoder enabled; the lower audio layer therefore does have a synthesis route, subject to a usable Timidity instrument configuration. What is missing is a supported Song/Content extension mapping, asset/instrument policy, and retained test showing that this route works in CNA rather than merely compiling below it.

The analysis gives three options: pre-render every track to OGG/FLAC against a chosen SoundFont ahead of time (lowest implementation risk, but a licensing/distribution question for the rendered assets and the SoundFont itself); expose and verify a reusable CNA-level MIDI route over the available decoder (cleaner for future games, but scope beyond what one port should decide unilaterally); or keep a Free Eggbert-specific MIDI service bridging through free-api’s own existing TinySoundFont path while every other subsystem moves to CNA. Its staged recommendation uses the game-specific bridge for an initial, behavior-preserving port, option one (pre-rendered assets) for a final low-maintenance release if asset policy permits it, and option two (a real CNA-level MIDI path) only if neither of the other two is acceptable.

75.9.5 Networking reconstruction defect

CNetwork::Receive reads into a local 500-byte stack buffer but never copies the data to the caller’s destination. This source-proven defect makes the current decompiled reconstruction an unreliable behavioral specification for networking. Future multiplayer work needs a versioned wire format and two-process tests against intended behavior, not an attempt to preserve this function’s omission.

75.9.6 Renderer progression and licensing boundary

The recommended sequence starts on SDL_RENDERER for focused portable 2D validation, then builds the same port against FREEDIRECT as a differential bridge between the old and new public APIs. An independent OPENGLES3 run supplies evidence beyond one renderer. Part IV describes those identities and their implementation routes. The feasibility document predates the renderer naming migration and calls this identity EASYGL; it names the same shared implementation family, not a still-selectable public value. FREEDIRECT is a migration tool, not the target architecture: a build linking it has adopted CNA’s public API but retains the DirectDraw-era dependency.

Free Eggbert declares GPL-3.0, while CNA declares Ms-PL. Combining and distributing them in one linked program requires a licensing review. Neither the feasibility document nor this book makes that legal determination.

75.10 Porting implications

The call-site method produces a narrower compatibility layer than a speculative full-surface implementation, but one aligned with the two target games. It exposed details that an API-only reading could miss, including GetDC’s gameplay role and the PCMWAVEFORMAT layout requirement. For a native CNA port, the same method identifies stable seams, timing assumptions, evidence gaps, and licensing questions before code is moved.

Type at least three characters. Results are ranked by how often and where the words occur.