Date: 2026-07-08
Author: Prepared by Claude Code, at the request of Robert Vokac (project owner)
Scope: free-eggbert repository (this repo) + the Ghidra project
speedy-blupi-2_robertvokac/speedy-blupi-2 on the local library drive
(drive.openeggbert.com), plus the wider openeggbert GitHub organization
that supports it.
Purpose of this document: a single reference that (a) inventories exactly what exists today across the source repo and the Ghidra project, (b) analyzes what is missing to call the decompilation "done" in two senses — behaviorally correct and buildable as every historical variant of the game from one codebase — and (c) lays out a workflow, sustainable over an arbitrarily long horizon (the user has explicitly framed this as "even if it takes 40 more years of my life, 1000 hours a year"), for using AI coding agents (Claude Code, ChatGPT, and similar) as a force multiplier on that work without the effort collapsing under its own weight after the first few months.
This is a working document, not a one-time report. It is meant to be re-read, edited, and kept current as the project evolves — see Section 9 for why that matters more than almost anything else here.
- Executive Summary
- Current State Inventory
- Gap Analysis
- Goal 1 — Behaviorally Faithful Decompilation
- Goal 2 — One Codebase, Many Variants via Build Macros
- Long-Term Roadmap (Horizons, Not a 40-Year Task List)
- AI-Assisted Workflow — Claude Code, ChatGPT, and Friends
- Tooling Stack
- Knowledge Persistence — So Progress Survives Decades and Model Resets
- Concrete Next Actions (First 90 Days)
- Risks and Limitations
- Ecosystem Map — the Wider
openeggbertOrganization - Appendix A — Ghidra Project Binary Inventory
- Appendix B — Source File Status Table
The good news: this project is much further along than "starting a decompilation from scratch." Three things are already in place that a typical reverse-engineering effort spends its first year building:
- A partially working, buildable, playable C++ reconstruction
(
free-eggbert, ~31,700 lines acrosssrc/+include/) that already runs on Windows (native DirectX and via a compatibility shim), Linux, Web (Emscripten), and has early Android support. - A mature Ghidra project with roughly 20 imported binaries spanning the entire Blupi lineage (Planet Blupi demos and full version, Speedy Blupi demo/1.0/2.2, four Speedy Eggbert 1.x variants, Speedy Eggbert 2, a Russian localization, French/"Home" variants, and the Windows Phone .NET port DLLs), Version Tracking correlation sessions between several of these binaries, and captured debugger traces from live runs (including of the DirectPlay multiplayer service and a MIDI synth helper process). This is exactly the kind of cross-version correlation data that turns "guess what this function does" into "confirm what this function does by diffing five variants of it."
- A wider
openeggbertorganization of ~25 sibling repositories (free-api,free-direct,sharp-runtime,planetblupi, asset/tool repos, etc.) that already contains a proven, working convention for long-horizon AI-agent-driven development (CLAUDE.md/NEXT.md/plan.md) — it simply hasn't been applied tofree-eggbertyet. See Section 12.
The two goals stated for this audit, restated precisely:
- Goal 1: Finish decompiling the game so it is fully playable and behaviorally identical to the original, not just "close enough."
- Goal 2: Make the same decompiled source tree buildable, via
compile-time configuration (macros/CMake options), as any of the
historical
.exevariants — Speedy Blupi (demo, 1.0, 2.2), Speedy Eggbert (1.0, 1.1, demo, SE/Valueware, 2) — rather than needing a separate fork or hand edit per variant.
The honest framing of "40 years / 40,000 hours": a literal year-by-year task list for four decades is not a meaningful engineering artifact — nobody can predict 2050's tooling, and the plan would be stale within a year regardless. What is meaningful, and what this document provides, is (a) a process that scales indefinitely without degrading — because progress is stored outside any single AI conversation, in the repository itself — and (b) a phased roadmap in Horizons (Section 6) with the first 1-2 Horizons planned concretely and later Horizons deliberately left as a lighter-touch strategy to be re-planned as they approach. Time budget (1000 h/year) is treated as a throughput constraint that should shape scope per Horizon, not as a literal Gantt chart.
| Aspect | State |
|---|---|
| Language | C++20 |
| Size | ~31,700 lines across 24 .cpp files + 19 .hpp headers |
| License | GPLv3 |
| Build systems | CMake (primary; Free Direct/SDL3 backend), Visual Studio 2022 .sln/.vcxproj (native DirectX 3), legacy MSVC5 .dsp (msvc5/sb2decomp) |
| Platforms working today | Windows (native DirectX, and via free-api/free-direct shim), Linux (SDL3-backed FREEDIRECT backend), Web/WASM (Emscripten, with IndexedDB persistence), Android (early) |
| Dependencies | dxsdk3 (DirectX 3 SDK, submodule), third_party/SDL, SDL_image, SDL_mixer (submodules, vendored by default), bass/bassmidi (optional alt audio backend), sibling repos free-api and free-direct (add_subdirectory(../free-api), add_subdirectory(../free-direct) — not vendored, must exist as sibling checkouts) |
| Existing regression tests | A test_* binary suite already exists in the build tree (test_eggbert_loop, test_planetblupi_loop, test_gdi_regressions, test_winuser_regressions, test_mci_sequences, test_joystick_regressions, test_loadstring_regressions, test_timer_regressions, test_file_regressions, test_resources, test_header_compile, test_input_pipeline, test_mci_avivideo_regressions, test_timeb, test_file_paths) — these are built via free-api's own CMakeLists.txt (add_subdirectory(../free-api FREE_API)), not from sources inside free-eggbert. This is a real, working regression-test culture already in place one repo over — it should be extended into free-eggbert itself, see Section 7.4. |
| Symbol-name provenance | Many demangled function/variable names come from ILSpy-inspecting WindowsPhoneSpeedyBlupi.dll, the 2013 Windows Phone C#/.NET port of Speedy Blupi, archived by forum user "Ч.У.Ш" (4PDA). This port is structurally closer to source-level C++ than raw Ghidra pseudocode and should be treated as a first-class reference, not a footnote. |
| Original decompilation authorship | This codebase did not start with Robert Vokac. git log --reverse shows the project began as HMVocaloid/Speedy-Eggbert-2-Source-Code-Decomp on GitHub, with early contributions from a collaborator using the handle jummy (whose initials also appear as "JUMMY WAS HERE" in util/extract_*.py, the scripts that pull static tables directly out of BLUPI.exe by hardcoded file offset). Robert Vokac's free-eggbert is a continuation/fork of that community effort. Action: the original HMVocaloid repo (issues, any wiki, alternate branches) has not been re-examined in this audit and may hold additional notes worth mining. |
| Known compile-time variant flags (already exist, just not build-matrixed) | include/def.hpp: _DEMO, _EGAMES, _SE, _INTRO, _CD, _BYE, _LEGACY, _DREAM, _BASS — all plain #defines that must be hand-edited and the whole project rebuilt to switch variant. This is the exact mechanism Goal 2 needs to replace with a CMake-level, per-target configuration (Section 5). |
Known decompilation artifacts/bugs (already documented in README / prior DOC.md) |
Double CPixmap::Create() call during init (likely spurious); at least one function with swapped parameter order from miscompiled calling-convention recovery; delete this patterns copied literally from decompiler output; widespread apparently-uninitialized locals that are artifacts of the original compiler's aggressive stack-slot reuse (suppressed today via MSVC /wd4700 /wd4703) — the _LEGACY flag exists specifically to let you toggle "keep the historical bug" vs "apply the confirmed fix." |
| Files flagged as needing the most work (from README, in priority order) | event.cpp (5,878 lines — input/phase dispatch, by far the largest and most tangled file), decblupi.cpp (4,395 lines — player physics/vehicles), decio.cpp, decdesign.cpp, decblock.cpp, decmove.cpp, decnet.cpp, decor.cpp, misc.cpp |
| Files considered tentatively complete (still need verification) | blupi.cpp, button.cpp, jauge.cpp, menu.cpp, movie.cpp, network.cpp, pixmap.cpp, sound.cpp, wave.cpp |
Explicit TODO/uncertain markers found in source (2026-07-08 grep) |
11 inline markers, concentrated in decblock.cpp, decdesign.cpp, and event.cpp (e.g. decblock.cpp:16: //TODO: rewrite this like a human) — a small, honest number; the actual gap is almost certainly larger and simply undocumented (see Section 3). |
| Working-tree note | DOC.md (a prior comprehensive technical reference, added in commit dae5652) is currently deleted in the working tree but not committed. Its content overlaps substantially with Sections 2, 3 and Appendix B of this document — this audit effectively supersedes and extends it. Recommend deciding whether to restore, delete for good, or fold its remaining unique content (the full TYPE_*/ACTION_*/SOUND_* enumerations) into a docs/ reference page; not touched by this audit. |
Beyond the Ghidra project (Section 2.3) and the source repo, the local library drive holds substantial primary and secondary source material that is directly usable for decompilation verification work:
Blupi/Games/Speedy_Blupi_(Windows)/—Bugs/,Covers/,Images/,Level/,Levels/,Mods/,Other/,Proposal_of_new_features/,Screens/,spritesheets/,Troubleshooting/: community-collected bug reports, level dumps, sprite sheets and mods (e.g.restoring_dream_blocks_in_speedy_blupi_2/Eggbert2_customobjgfx.exe,floppy_mod/sb2_1.4M.exe) — several of these mods are themselves patched binaries, i.e. more variants to eventually correlate.Blupi/Games/Speedy_Blupi_(Windows_Phone)/— resources and screenshots for the C#/.NET port that is the source of many symbol names (Section 2.1).Blupi/Games/Planet_Blupi/— the predecessor engine,pblupi-e.exe.other/— Blupi comics (BLUP_COMICS_by_Daniel_Roux*.zip), the Blupi at Home and Blupimania spin-off screenshots/binaries,speedydgVoodoo.zip(a Voodoo/3dfx compatibility patch — evidence of yet another binary variant),vr1tovr2_v2.2.zip(a version-upgrade patch, useful for understanding exactly what changed between two shipped builds), and a cropped promotional animation.
None of this has been exhaustively catalogued function-by-function; it should be treated as a priority-ordered backlog of ground truth, not as something to process all at once.
The project directory (Speedy Blupi 2.rep) is a genuine, non-trivial
Ghidra project, not an empty shell. Reading its internal index directly
(idata/~index.dat) recovers the full catalog of everything ever
imported, including items that no longer show up anywhere else on disk.
See Appendix A for the full table. Highlights:
- ~11
Programentries (importable, disassemblable binaries): the main targetSpeedyBlupi2_v2.2_EN.exe, plusSpeedyBlupiDemo_v0.8_EN,SpeedyBlupi_v1.0_EN,SpeedyEggbert_v1.0_EN,SpeedyEggbert_v1.1_EN,SpeedyEggbertDemo_v1.0_EN,SpeedyEggbertSEValueware_v1.0_EN,SpeedyEggbert2_v1.0_EN,ReaktivniyEgbert_v1.1_RU(a Russian localization — "Reaktivniy Egbert" ≈ "Speedy Eggbert"),PlanetBlupi_v1.7_EN,PlanetBlupiDemo_v1.6_EN,PlanetBlupiDemo_v1.8_EN, andBLUPIF.EXE/BLUPIH.EXE(French / another regional or "Home" build — needs confirmation). - Seven named Version Tracking correlation sessions already created
between specific binary pairs, e.g.
SB2 v2.2 to SE2 v1.0,SB2 v2.2 to PB v1.7,PB v1.7 to PB v1.6 demo,PB v1.7 to SB2 v2.2,SB2 v2.2 to SB v1.0,SB v1.0 to SE v1.0,SB v1.0 to RE v1.1,SB2 v2.2 to FwB,SB2 v2.2 to BaH(the latter almost certainly correlates againstblupi-home-demo.exe, "Blupi at Home", found separately inother/blupi_at_home/). This is the single most underused asset in the whole project — Ghidra's Version Tracking feature exists precisely to propagate confirmed function names, types and even matched-decompiled semantics from one binary to a related one. If even one of these ~11 programs has been analyzed to high confidence, that confidence can be pushed into the others automatically instead of re-derived by hand. - Debugger trace captures ("New Traces" folder) from live dynamic
analysis sessions dated July 2024, including traces of
dplaysvr.exe(the DirectPlay multiplayer service — relevant to finishingdecnet.cpp/network.cpp) andVirtualMIDISynth-synth.exe(relevant to the music/MIDI path). This confirms dynamic analysis/debugging (not just static disassembly) has already been used at least once and the harness for it exists. - The DirectX 3/5-era SDK headers (
include/d3d.h,ddraw.h,dinput.h,dmusicc.h, etc., dated 1999) are imported into the Ghidra project's data-type archive — the same SDK vendored as thedxsdk3submodule infree-eggbert— so Ghidra's decompiler already has accurate struct/COM-interface layouts for DirectDraw/DirectSound/ DirectInput/DirectMusic calls rather than guessing them. - System DLLs imported for import resolution:
ddraw.dll,ddrawex.dll,dinput.dll,dinput8.dll,dmusic.dll,dplay.dll,dplayx.dll,dsound.dll,gdi32.dll,kernel32.dll,ntdll.dll,user32.dll,winmm.dll. util/extract_phasetable.py,extract_pixtables.py,extract_tableblupi.py,extract_tablemirror.pyinfree-eggbertread hardcoded byte offsets directly out ofBLUPI.exe("speedy blupi 2.2 english ver" per their own comments) to recover static data tables (phase table, icon tables, mirror table). These offsets are specific to that one exact binary — they will silently read garbage (or crash) against any other variant, which is exactly the failure mode Goal 2 needs to design away from (Section 5).
Cross-referencing the README's own "files requiring the most attention"
list, the 11 inline TODO markers, and the DOC.md "Known Issues"
section against what a from-scratch audit would expect to find, three
categories of gap emerge — worth keeping separate because they need
different tools and different AI-assistance strategies:
Most of event.cpp, decblupi.cpp, decnet.cpp, decdesign.cpp,
decio.cpp. The code compiles and mostly runs, but fidelity to the
original binary's exact behavior is unverified — the only way to close
this gap is systematic diffing against ground truth (Ghidra
pseudocode of the matching function in SpeedyBlupi2_v2.2_EN.exe, cross-
checked via the Version Tracking sessions against the other ~10
variants, and — where genuinely ambiguous — against Planet Blupi's
released source per the project's own stated convention). Note that
planetblupi is not just archived text to read — it is a real sibling
repository (~41,270 lines of C++, GPLv3 lineage via Daniel Roux/Epsitec
SA) that is itself buildable and runnable today via the same
free-api/free-direct stack free-eggbert uses (Section 12.1).
Prefer building and running it side-by-side over relying on memory of
what its source does.
artifact of decompilation, not of the original design)
Double CPixmap::Create(), the parameter-swap bug, delete this
patterns, and the large class of "uninitialized" variables that are
really register-allocation reuse from the original optimizing compiler.
These need a human/AI reviewer who understands why Ghidra produces this
pattern (stack frame reuse under /O2-class optimization, C++ vtable
thunks, etc.) rather than a naive "fix the warning" pass — naive fixes
here are exactly where an AI assistant without ground-truth grounding
will confidently introduce new bugs. The book the project owner has
already purchased (Software Reverse Engineering: Navigating x86 Windows
Applications with Ghidra) directly targets this skill.
Everything in the repo today targets one binary
(SpeedyBlupi2_v2.2_EN.exe) even though the Ghidra project already has
~10 siblings analyzed. The util/extract_*.py hardcoded-offset scripts
are the sharpest example of this: they are correct for exactly one file
and will not generalize. Goal 2 (Section 5) exists to close this gap
deliberately, not as an afterthought once Goal 1 is "done" — doing it
concurrently is cheaper, because every variant-specific quirk discovered
along the way informs the behavioral-fidelity work in 3.1.
The cross-platform build (CMake/SDL3, Emscripten/Web with IndexedDB
persistence, early Android), the free-api/free-direct compatibility
shim architecture, and the existing test_* regression binaries are
functioning infrastructure. AI-assisted effort should route around
these, not through them.
The target is not "the game runs" (already true) but "the game's observable behavior — physics, AI, save format, network protocol, editor — is provably the same as the original binary's, function by function, with the confidence level tracked explicitly" (see Appendix B and Section 9 for how "provably" is operationalized without requiring literal byte-identical recompilation everywhere).
Recommended working definition of "done" per function, in increasing order of confidence — use this exact ladder in the knowledge-persistence ledger (Section 9):
- Not started — untouched decompiler output or missing entirely.
- Drafted — compiles, runs, "looks plausible," not checked against the binary.
- Behavior-verified — manually or automatically compared against
the original
.exe's observed behavior (gameplay trace, save-file round-trip, network packet capture) for representative inputs and found equivalent. - Structurally verified — the C++ has been diffed line-by-line against Ghidra's decompilation of the same function across all correlated variants (via Version Tracking) and against the Windows Phone C# port where available, with no unexplained divergence.
- Byte-matched — recompiling this translation unit with a period-
correct toolchain reproduces the original machine code exactly (or
the diff is fully explained, e.g. a different register allocation
with identical semantics). This is the gold standard used by mature
decompilation communities (Nintendo 64/GameCube-era "matching decomps"
like Super Mario 64, Ocarina of Time, Paper Mario; and — much closer
to this project's actual era and toolchain — the LEGO Island
(
isledecomp) project, which built tooling (reccmpand friends) specifically for MSVC-compiled, DirectX-era, C++ Win32 games. That project is worth studying directly as the closest known precedent — verify its current state and license before adopting any of its tooling, but the methodology (function-level binary diffing against a period MSVC build) transfers almost directly to Speedy Blupi 2.
Levels 1-3 are achievable now, with the current toolchain, and should be
the near-term focus. Level 5 is a multi-year stretch goal gated on
recovering (or closely approximating) the original build environment —
msvc5/sb2decomp in this repo already hints at MSVC 5/6 being the
original toolchain, consistent with the DirectX 3 SDK date (1999).
Variant selection is a single, flat set of #defines in include/def.hpp
(_DEMO, _EGAMES, _SE, _INTRO, _CD, _BYE, _LEGACY, _DREAM,
_BASS) that must be hand-edited before each rebuild. This works for
"one developer building one variant at a time" but cannot express "build
all N known variants in one CI run" or "add a 12th variant without
touching shared code."
Given the Ghidra inventory in Appendix A, the known variants that plausibly belong on this ladder are at minimum:
| Variant family | Binaries (from Ghidra project) | Notes |
|---|---|---|
| Speedy Blupi | SpeedyBlupiDemo_v0.8_EN, SpeedyBlupi_v1.0_EN, SpeedyBlupi2_v2.2_EN |
SpeedyBlupi2_v2.2_EN is the current decompilation target |
| Speedy Eggbert (1) | SpeedyEggbert_v1.0_EN, SpeedyEggbert_v1.1_EN, SpeedyEggbertDemo_v1.0_EN, SpeedyEggbertSEValueware_v1.0_EN |
_SE/_EGAMES flags already exist for exactly this family |
| Speedy Eggbert 2 | SpeedyEggbert2_v1.0_EN |
English rename/re-release of Speedy Blupi 2 |
| Localizations | ReaktivniyEgbert_v1.1_RU, BLUPIF.EXE/BLUPIH.EXE |
Confirm exact French/Home mapping before building targets for these |
| Mods (lower priority) | Eggbert2_customobjgfx.exe, sb2_1.4M.exe (floppy mod) |
Real historical binaries but derivative — treat as stretch targets, not core matrix |
Planet Blupi variants are a related but separate engine/repo
(planetblupi, see Section 12) and should stay out of free-eggbert's
build matrix even though they share Ghidra Version Tracking correlations
useful for cross-checking.
Move from "one #define set, hand-edited" to a CMake variant matrix:
- Introduce a single variant descriptor (e.g.
cmake/variants.cmakeor a small JSON/CMake data file) listing each known target with: display name, the_DEMO/_EGAMES/_SE/_INTRO/_CD/_BYE/_LEGACY/_DREAMvalues it needs, whichgamefiles/asset set it expects, and (once known) which of the hardcoded-offset tables inutil/extract_*.pyneed re-extracting for that binary. - Replace the raw
#defines indef.hppwith values driven bytarget_compile_definitions(<target> PRIVATE _DEMO=$<...>)per CMake target, socmake --build . --target speedy_blupi_2_2_enand--target speedy_eggbert_demo_1_0_encan coexist from one configure, one source tree, zero manual edits. - Retire the hardcoded byte-offset extraction scripts as a manual,
per-release, run-once step; replace them with either (a) values
already recovered into the decompiled source as named constants/
tables (preferred, since then they're variant-agnostic and don't need
BLUPI.exeat build time at all), or (b) a small offset-table per variant, keyed by the same variant descriptor, if a table genuinely cannot be recovered as source. - Add one CI matrix job per variant target (build + smoke-run against
that variant's
gamefiles/, once available) so a regression in one variant is caught immediately instead of silently accumulating.
This should be sequenced early (see Section 10), not after Goal 1 is
"finished" — most of the behavioral-fidelity work in Section 4 will
naturally surface variant-specific branches (_EGAMES/_SE/_CD gate
real behavioral differences, not cosmetic ones), and it is far cheaper to
express those as data in the variant descriptor from the start than to
retrofit them later.
Framed against a 1000 h/year budget, decades-scale but re-planned as it approaches:
Horizon 1 — Foundation (roughly 0–2 years, ~1000–2000 h):
Behavior-verify (Section 4, level 3) the highest-priority files in
priority order (event.cpp, decblupi.cpp, decio.cpp, decdesign.cpp,
decblock.cpp, decmove.cpp, decnet.cpp, decor.cpp, misc.cpp).
Stand up the knowledge-persistence ledger and CLAUDE.md/NEXT.md
convention (Section 9) first, before deep behavioral work, so nothing
done in this Horizon is lost to context resets. Land the CMake variant
matrix (Section 5) for at least the Speedy Blupi and Speedy Eggbert
English variants. Stand up an automated gameplay-replay regression
harness (Section 7.4) using the existing demo3xx.blp recordings.
Horizon 2 — Structural verification & remaining variants (roughly
2–5 years, ~2000–3000 h): Extend behavior-verification to
structural-verification (level 4) across the full file list. Extend the
variant matrix to the localizations and remaining variants once their
exact identity is confirmed. Begin recovering the original build
environment (MSVC 5/6 + DirectX 3 SDK) well enough to attempt level-5
byte-matching on a handful of small, self-contained functions as a proof
of concept — this is the point at which studying isledecomp/reccmp-
style tooling in depth pays off.
Horizon 3 — Byte-matching & preservation depth (roughly 5–15 years): Scale byte-matching as far as it is economical to go (it will likely never be 100% for a C++ binary of this size and age — MSVC C++ ABI quirks, inlining, and vtable layout make this categorically harder than the C-only 90s console decomps that pioneered the technique). Deepen platform ports (Android, further web work) on top of a codebase that is by now trustworthy enough to port confidently.
Horizon 4 — Maintenance & handoff (15–40 years): By this point the practical goal shifts from "verify more" to "keep it building, keep it documented, keep the org's institutional knowledge (Section 9) legible to whoever — human or AI — picks it up next," including this user personally at a very different life stage, or a successor maintainer. Do not attempt to plan this Horizon's task list now; re-derive it when Horizon 3 is closing.
The actual lesson from framing it this way: the binding constraint on a project like this is not calendar time, it is whether institutional knowledge compounds or resets. A plan that gets 90% of the way to "done" in year 3 and then has to be re-discovered from scratch in year 4 because nothing was written down is worse than a plan that moves slower but never re-derives the same fact twice. Section 9 is the single most important section in this document for that reason.
This section is the direct answer to "how do I use AI so it actually helps with the decompilation, even across tens of thousands of hours." The short version: AI is extremely good at the parts of this task that are "read a lot, compare carefully, write it up clearly" and extremely dangerous, unsupervised, at the parts that are "assert what x86 assembly means without checking." Structure the workflow so the first kind of task is delegated freely and the second kind is always checked against ground truth (Ghidra's own disassembly/decompilation, or actual program behavior) before being trusted.
- Reading and explaining Ghidra pseudocode. Given raw decompiler
output for a function plus the surrounding data types (already
present, given the imported DirectX headers), an LLM is good at
proposing idiomatic, readable C++ that preserves semantics — variable
naming, restructuring
goto-heavy control flow into loops, spotting where a pattern matches a known idiom (e.g. a state machine overWM_PHASE_*). - Cross-referencing across large amounts of text — e.g. "does this
function's Ghidra pseudocode in
SpeedyBlupi2_v2.2_EN.exematch the corresponding function surfaced by theSB2 v2.2 to SB v1.0Version Tracking session inSpeedyBlupi_v1.0_EN.exe, and does either match the corresponding class in the Windows Phone C# port?" This is exactly the kind of multi-source diffing that benefits from an agent that can hold several files in context simultaneously and is patient about it. - Mechanical, high-volume, low-risk sweeps: generating Doxygen
comment scaffolding, auditing every
TODO/uncertain marker and classifying it, writing the CMake variant-matrix boilerplate from a descriptor, drafting the first pass of a new regression test from an existing demo replay. - Writing and maintaining the knowledge-persistence ledger itself (Section 9) — this is a text-generation and organization task AI agents excel at, and it is the highest-leverage use of AI time in the entire project because of the compounding effect described in Section 6.
- Asserting x86 calling-convention or optimization semantics from memory. LLMs will confidently describe register-allocation or stack-layout behavior that sounds plausible and is wrong for this specific compiler/optimization level. Ground every such claim in Ghidra's actual disassembly of the actual binary, not in the model's prior knowledge of "how MSVC usually does X." This is precisely why the project owner studying Software Reverse Engineering: Navigating x86 Windows Applications with Ghidra is high-value personal investment that AI cannot substitute for — it builds the judgment needed to tell when the AI's explanation of a decompiled function is actually consistent with the disassembly versus merely fluent-sounding.
- "Fixing" apparently-uninitialized variables or
delete thispatterns by local reasoning. As noted in Section 3.2, several of these are known artifacts with specific, non-obvious root causes. An AI asked to "clean up this warning" without that context will produce confident, plausible, wrong code. Always frame these tasks as "explain why Ghidra produced this pattern here, citing the actual disassembly," not "fix this." - Silent scope creep on a 32,000-line, decades-long project. Without an explicit ledger of what's already verified (Section 9), an AI session has no way to know whether it's re-deriving a fact already established two years ago or discovering something new. This is a process failure, not a model failure, and the fix is process (Section 9), not a better prompt.
- Use Claude Code (or an equivalent agentic coding CLI) as the primary driver, not a plain chat interface, precisely because this task needs to read Ghidra exports, source files, and reference binaries concurrently, and to write both code and the persistent ledger as it goes — a chat window that can't touch the filesystem forces constant manual copy-paste and loses the audit trail.
- Investigate a Ghidra↔agent bridge so the agent can query the live Ghidra project directly (list functions, pull a specific function's decompilation, pull xrefs, rename/retype a symbol) instead of a human manually exporting pseudocode and pasting it in. Community bridges for this exist in the Ghidra ecosystem (commonly implemented as a Ghidra script/plugin exposing an HTTP or MCP-style interface, sometimes paired with Ghidrathon for Python scripting inside Ghidra itself) — evaluate current options against this Ghidra version and this project's security posture before adopting one; do not assume any specific named tool is still maintained without checking.
- For large mechanical sweeps (e.g., "classify every one of the 11
TODOmarkers plus every undocumented function inevent.cppby confidence level," or "diff this function across all 6 correlated variants and report divergences"), use a multi-agent fan-out (parallel review agents, one per file or per variant-pair, each reporting structured findings) rather than one long serial conversation — the task is naturally parallel and the volume would otherwise blow past any single context window. - Keep session scope narrow and let the ledger carry continuity. A
session that opens by reading
NEXT.md(today's queue) anddocs/decomp-status/<file>.md(this file's confirmed facts), does one bounded unit of work, and closes by updating both, is far more durable across a 40-year horizon than a session that tries to hold the whole project's state in its own context.
The game already ships prerecorded demo playback files
(demo300.blp–demo309.blp) and a fully-specified save-state struct
(DescSave, ~56 KB, covering world grid, all moving objects, player
state, doors, cheats). This is, almost by accident, exactly the raw
material a "matching decompilation" project needs for behavior-level
regression testing:
- Run each demo recording through the original binary (under Wine,
DOSBox-adjacent tooling, or a VM/emulator as needed — several original
binaries are already in the Ghidra project and library) and capture
DescSavestate at fixed intervals or on level-end. - Run the same recording through
free-eggbert's own simulation and capture the same state. - Diff the two. Any divergence is a concrete, reproducible bug report pointing at a specific tick and a specific field — vastly more actionable than "movement feels slightly off."
This directly extends the existing test_planetblupi_loop /
test_eggbert_loop regression tests already present via free-api, so
it is additive to proven infrastructure rather than a new invention. It
is one of the highest-leverage single investments available for
Section 4's behavior-verification work, because once built it runs
unattended and produces an ever-growing, self-checking confidence set —
exactly the kind of artifact that keeps paying off across a multi-decade
horizon instead of being redone by hand each session.
| Tool | Role today | Recommended role going forward |
|---|---|---|
| Ghidra | Primary static analysis, Version Tracking across variants, live debugger traces | Keep as the system of record for "ground truth about the binary." Consider a scripted/agent-queryable interface (Section 7.3) so AI sessions can pull decompilation directly instead of manual copy-paste. |
| IDA | Used per README, role not detailed further here | Useful as a cross-check against Ghidra's decompiler on the hardest functions — different decompilers sometimes disagree in instructive ways. |
| ILSpy | Inspecting WindowsPhoneSpeedyBlupi.dll for demangled names/structure |
Continue treating the WP8 C# port as a first-class reference implementation, not just a naming source — its control flow is much closer to source-level than raw x86 pseudocode. |
| Visual Studio 2022 | Native Win32/DirectX build target | Keep for the "does it work on real Windows/DirectX 3" check. |
Legacy MSVC 5/6 (msvc5/sb2decomp) |
Present but apparently not actively used for builds today | Revive deliberately once byte-matching (Section 4 level 5) becomes a near-term goal — matching the exact original compiler is a hard requirement for that level, not a nice-to-have. |
| CMake + SDL3 | Cross-platform build (Linux/Web/Android), FREEDIRECT backend |
Extend with the variant matrix (Section 5) and a CI job per variant. |
| Emscripten | Web/WASM build with IndexedDB persistence | Working; low priority for further audit attention. |
isledecomp/reccmp-style matching-decomp tooling (external project, LEGO Island) |
Not currently used | Evaluate directly as the closest known precedent for MSVC, DirectX-era, C++ byte-matching decompilation — verify current maintenance status and license before depending on it; the methodology is the main asset even if the tooling itself needs adaptation. |
planetblupi (sibling repo) |
Buildable, runnable predecessor engine (Section 12.1) | Use as a live behavioral reference wherever free-eggbert's decompiled behavior is ambiguous — build and run it, don't just read it. |
tiled-blupi (sibling repo) |
Tiled-editor extensions + a documentation site for the .blp format family (blp-format.html, map-properties.html, editing-workflow.html) |
Check this before re-deriving .blp structure by hand — it may already document parts of what decio.cpp, dectables.cpp, and the util/extract_*.py offset scripts currently reverse-engineer from scratch. |
speedyblupi-data (sibling repo) |
Canonical original asset/cover archive, GPLv3 | Authoritative source for assets and release/version metadata across the whole variant family (Section 5.2). |
| Claude Code / ChatGPT / equivalent agentic AI | Ad hoc | Formalize per Section 7 and 9 — narrow-scope sessions, ledger-driven continuity, multi-agent fan-out for parallel mechanical work, always grounded in Ghidra/binary ground truth for anything asserting low-level semantics. |
| The purchased book (Software Reverse Engineering: Navigating x86 Windows Applications with Ghidra) + existing x86 references | Not yet studied (per user) | Pair reading with practice: after each chapter, apply its specific technique to one currently-unresolved function in event.cpp or decblupi.cpp and record the outcome in the ledger (Section 9) — this both cements the skill and produces project progress simultaneously, rather than reading in isolation from the work. |
This is the mechanism that makes the "40,000 hours" framing survivable at all: an AI conversation's context is not where project knowledge should live. It must live in the repository, as plain text, so that any future session — this year, or in 2050, run by this user or by whoever inherits the project — can reconstruct full context in minutes instead of re-deriving it.
The openeggbert organization already has a proven convention for this
in free-direct and sharp-runtime (see Section 12 for specifics once
confirmed) — the recommendation here is to apply that same convention
to free-eggbert, not invent a new one:
CLAUDE.md(repo root) — stable, slowly-changing context: what this project is, the architecture (free-api/free-directdependency boundary, variant matrix once built, where assets live), conventions (e.g. "always check the Version Tracking session before asserting a function's behavior differs across variants"), and pointers to this audit document and the status ledger below. This is what a brand-new AI session reads first.NEXT.md(repo root) — the current, short-lived task queue: what is being worked on right now and what's queued next. Gets rewritten constantly; not a history.plan.md(repo root ordocs/) — the longer-horizon roadmap (essentially a living version of Section 6), re-derived/updated at Horizon boundaries rather than every session.docs/decomp-status/<source-file>.md(new, recommended) — one file per source file insrc//include/, listing every non-trivial function with its current confidence level on the ladder from Section 4 (Not started → Drafted → Behavior-verified → Structurally-verified → Byte-matched), the date/session it was last touched, and any known-artifact notes (Section 3.2) so nobody "fixes" a documented artifact by accident. This is the single ledger every session should read before touching a file and update before finishing.docs/variant-matrix.md(new, recommended) — human-readable companion to the CMake variant descriptor (Section 5.3): which binaries map to which build target, what's confirmed vs. guessed (e.g. the exact identity ofBLUPIF.EXE/BLUPIH.EXEand the "FwB" Version Tracking session target).
The habit to build, more than any specific file format: end every substantial AI-assisted session by updating the ledger before ending the session, even if the code change itself feels too small to bother documenting. The ledger is worthless if it silently falls out of date; it is the entire point of this section.
Ordered so each step makes the next one cheaper, not by file-priority alone:
- Stand up the ledger first (Section 9): add
CLAUDE.md,NEXT.md,docs/decomp-status/(seed it from the README's existing "most attention" / "tentatively complete" lists — that's a free first draft), anddocs/variant-matrix.md. Decide what to do with the deleted-but-uncommittedDOC.md(restore, fold intodocs/, or discard) as part of this pass. - Confirm the ambiguous Ghidra inventory entries: what exactly
BLUPIF.EXE/BLUPIH.EXEare, what "FwB" in theSB2 v2.2 to FwBVersion Tracking session refers to, and whatTelefou.xchandWORLD199.BLP(both individually imported into the Ghidra project) were imported for. Cheap to resolve now, confusing to leave open. At the same time, read throughtiled-blupi's.blpformat documentation (blp-format.html,map-properties.html,editing-workflow.html) — some of the ground this audit assumes still needs reverse-engineering (Section 3.3, theutil/extract_*.pyhardcoded offsets) may already be written up there. - Pick one already-"tentatively complete" file (e.g.
wave.cpporjauge.cpp— small, self-contained) and run it through the full confidence ladder (Section 4) end to end, including a first attempt at Ghidra Version-Tracking cross-checking against at least one other variant. The goal is to prove the pipeline end-to-end on a cheap example before spending real effort onevent.cpp. - Build the gameplay-replay regression harness (Section 7.4) using
demo300.blpfirst — even a partial version (compare a handful ofDescSavefields at level-end, original binary run manually once to capture a baseline) is enough to start catching behavioral regressions automatically. - Prototype the CMake variant matrix (Section 5.3) for exactly two
targets (
SpeedyBlupi2_v2.2_ENfull and demo) to validate the mechanism before generalizing to all ~10. - Only then resume systematic behavior-verification of
event.cppanddecblupi.cpp, now backed by the ledger, the regression harness, and the variant matrix — so that work compounds instead of evaporating the moment the session ends.
- C++ makes byte-matching categorically harder than the C-only 90s console decomp scene this methodology is borrowed from. Vtable layout, name mangling, inlining, and MSVC-specific ABI quirks mean 100% byte-matching may never be fully economical for a binary this size. Treat structural-verification (Section 4, level 4) as the realistic default target and byte-matching as a stretch goal for specific, high-value, self-contained functions rather than a blanket requirement.
- Provenance and legality: this is a preservation/interoperability project built on decompiling a commercial binary whose source was never released, using assets that "must be obtained from an original copy" per the existing README. Keep the project's existing framing (research/preservation/compatibility, GPLv3 for the reconstructed code only, assets not redistributed) explicit in the ledger so it isn't accidentally eroded as the project scales across decades and contributors.
- AI hallucination on low-level semantics (Section 7.2) is the single largest correctness risk introduced by leaning on AI more heavily — mitigate structurally (always ground in Ghidra/binary output), not by trusting the model more as it becomes more fluent.
- Tool rot over a 40-year horizon is a certainty, not a risk. Ghidra, Claude Code, and every other named tool in this document will change or be replaced multiple times before this project could plausibly be "done." This is exactly why Section 9's plain-text, tool-agnostic ledger — not a Ghidra project file, not a chat history — is the actual long-term asset.
- Scope discipline: the
openeggbertorg's ~25 sibling repos (Section 12) show a real risk of effort diffusing sideways into adjacent tooling projects. That may well be the right call for the org as a whole, but withinfree-eggbertspecifically, prefer finishing the decompilation of the currently-in-scope binaries before absorbing more.
free-eggbert is one repo in a ~25-repo personal organization
(/rv/data/development/github.com/openeggbert/) that has been under
active, AI-assisted development for years. Understanding this
ecosystem matters for two reasons: (a) several sibling repos are direct
dependencies or references for the decompilation work, and (b) the
organization has already converged on a working long-horizon AI-agent
workflow that simply hasn't been applied to free-eggbert yet.
| Repo | Purpose | Relevance to free-eggbert decompilation |
|---|---|---|
free-api |
MIT, C++20 reimplementation of a minimal ~1998 Win32 API subset | Direct build dependency (add_subdirectory(../free-api)) — lets decompiled Win32 calls compile without real Windows |
free-direct |
MIT, C++20 reimplementation of a narrow DirectX 3 (2D) subset on SDL3 | Direct build dependency — the backend free-eggbert links for DirectDraw/DirectSound/DirectPlay on non-Windows targets |
planetblupi |
Buildable, portable copy of Epsitec's original 1997 Planet Blupi source (built via free-api/free-direct), ~41,270 lines of C++, GPLv3 lineage (Daniel Roux / Epsitec SA) |
The most important reference in the whole ecosystem for Goal 1. This is not just "text to consult" — it is a live, compilable, runnable predecessor engine. Where free-eggbert's decompiled behavior is ambiguous, build and run planetblupi itself rather than relying on memory of what its source does. |
sharp-runtime / sharp-runtime_work |
C++23 reimplementation of a .NET subset (System::*) supporting the CNA/mobile-eggbert chain |
Not a direct dependency of free-eggbert, but see 12.2 — its process convention is the one to copy |
mobile-eggbert (+ -legacy, -libgdx) |
Port chain: WP8 XNA Speedy Blupi → ILSpy-decompiled C# → MonoGame → C++/CNA → LibGDX | This is the origin of the WindowsPhoneSpeedyBlupi.dll symbol names free-eggbert already borrows (Section 2.1) — treat as the same reference lineage, not a separate concern |
speedyblupi-data |
Canonical original asset archive (originals/, backgrounds/, covers/ incl. egames_scans, an analysis.fods spreadsheet), GPLv3 |
Authoritative source for gamefiles/-equivalent assets and box/cover metadata across releases |
tiled-blupi |
Tiled map-editor extensions for the Blupi .blp-family formats, with per-game folders for "Speedy Blupi" and "Speedy Blupi II" plus a documentation site (blp-format.html, map-properties.html, editing-workflow.html) |
The closest thing that already exists to a written .blp format specification. Read this before doing further work on decio.cpp/dectables.cpp/the util/extract_*.py offset scripts — some of that reverse-engineering may already be documented here. |
BlupiEdit |
Level/scene editor (CMake + web build); has its own plan.md but no confirmed README/CLAUDE.md yet |
Likely relevant to level-editor parity (decdesign.cpp) — worth a dedicated follow-up look, not yet fully characterized |
galaxy-eggbert, xna4-spec, cna*, easy-3d, easy-gl, fake-gl, mesh-craft, mesh-world, simple-3d, sprite-utils, rd4-to-gltf |
Broader OpenEggbert 3D/graphics framework and tooling (XNA-style engine, GL wrappers, scene formats, asset conversion) | Downstream/parallel creative and tooling work; not relevant to the Win32/DirectX3 decompilation itself — explicitly out of scope per the scope-discipline risk in Section 11 |
openeggbert |
Org-level meta repository/README | Context only |
free-direct and sharp-runtime both use the same convention for
surviving long, multi-session, multi-year AI-assisted development — this
is the concrete pattern Section 9 recommends adopting verbatim in
free-eggbert:
CLAUDE.md— a stable "project charter" that rarely changes: mission, explicit scope/non-scope boundaries, subsystem policies, coding conventions, and an explicit "Safety Rules for Claude Code" section (invariants an agent must never violate — e.g. dependency direction, what must never be committed, what always needs human sign-off).plan.md— the long-horizon roadmap. Infree-directthis is a literal numbered Phase 0…18 plan with a short "how to read this plan" preamble at the top. Insharp-runtime, planning has moved a step further: it's now database-driven — a git-ignoredplan.sqlite3withtask/tickettables is the actual live plan, andplan.md/plan_namespaces.mdare historical/pointer documents with ready-made SQL status queries. Forfree-eggbert's scale (a big but finite, well-enumerable function/file list — see Appendix B), a plainplan.mdis almost certainly sufficient to start; graduate to a database only if the per-function tracking indocs/decomp-status/(Section 9) grows unwieldy as flat files.NEXT.md— the living handoff/checkpoint document, with a consistent skeleton across repos: (1) project summary, (2) current status — build/tests/tools, (3) recent changes, (4) current blocker, (5) known bugs, (6) architecture notes, (7) useful commands, (8) next smallest tasks, (9) do-not-do-yet, (10) resume prompt. The top of the file always carries a dated "RESUME HERE" section. This file, not conversation history, is the actual cross-session/cross-year memory mechanism — a brand-new Claude Code session with zero prior context readsCLAUDE.md+NEXT.mdand is productive within minutes.prompt.md(seen insharp-runtime) — instructions for running a fully autonomous loop: at the start of every fresh context, readCLAUDE.md+NEXT.md, pull the next queued item, do it (classify / implement / test / commit), update the tracking record, and don't stop to ask permission except for genuinely ambiguous architecture decisions or destructive/irreversible actions. This is the concrete mechanism that lets a 1000-h/year, multi-year effort actually run as a long series of mostly-unattended agent sessions rather than requiring constant hands-on driving — directly applicable to Horizon 1 in Section 6 once the ledger (Section 9) exists to make it safe.
Adopting this exact four-file convention in free-eggbert (it currently
has none of them — only README.md, TODO.md, and a deleted-but-
uncommitted DOC.md) is one of the highest-leverage, lowest-risk actions
available, precisely because it is already proven elsewhere in this
user's own workflow rather than a novel process to validate from
scratch.
Recovered directly from Speedy Blupi 2.rep/idata/~index.dat on
2026-07-08. "Item" is the internal Ghidra catalog ID; useful if
cross-referencing against the project's own .prp metadata files later.
| Item | Name | Type |
|---|---|---|
00000000 |
SpeedyBlupi2_v2.2_EN.exe |
Program (current main decompilation target) |
00000010 |
SpeedyBlupiDemo_v0.8_EN.exe |
Program |
00000011 |
SpeedyBlupi_v1.0_EN.exe |
Program |
00000012 |
SpeedyEggbert_v1.1_EN.exe |
Program |
00000013 |
SpeedyEggbertDemo_v1.0_EN.exe |
Program |
00000014 |
SpeedyEggbert_v1.0_EN.exe |
Program |
00000015 |
SpeedyEggbertSEValueware_v1.0_EN.exe |
Program |
00000016 |
SpeedyEggbert2_v1.0_EN.exe |
Program |
00000017 |
ReaktivniyEgbert_v1.1_RU.exe |
Program (Russian localization) |
00000018 |
PlanetBlupiDemo_v1.8_EN.exe |
Program |
00000019 |
PlanetBlupiDemo_v1.6_EN.exe |
Program |
0000001a |
PlanetBlupi_v1.7_EN.exe |
Program |
00000035 |
BLUPIH.EXE |
Program (identity to confirm — likely "Home" variant) |
00000036 |
BLUPIF.EXE |
Program (identity to confirm — likely French) |
0000001d |
WindowsPhoneSpeedyBlupi.dll |
.NET assembly (source of many symbol names via ILSpy) |
0000001e |
WindowsPhoneSpeedyBlupi.resources.dll |
.NET resources |
00000008–0000000f |
ddraw.dll, dplay.dll, dplayx.dll, kernel32.dll, dsound.dll, gdi32.dll, user32.dll, winmm.dll |
System DLLs (import resolution only) |
00000026 |
WORLD199.BLP |
Imported level/data file (reason for standalone import unconfirmed) |
00000027 |
Telefou.xch |
Imported file, purpose unconfirmed |
0000001b |
SB2 v2.2 to SE2 v1.0 |
Version Tracking session |
0000001c |
SB2 v2.2 to PB v1.7 |
Version Tracking session |
00000021 |
PB v1.7 to PB v1.6 demo |
Version Tracking session |
00000022 |
PB v1.7 to SB2 v2.2 |
Version Tracking session |
00000023 |
SB2 v2.2 to SB v1.0 |
Version Tracking session |
00000024 |
SB v1.0 to SE v1.0 |
Version Tracking session |
00000025 |
SB v1.0 to RE v1.1 |
Version Tracking session |
00000037 |
SB2 v2.2 to FwB |
Version Tracking session (target "FwB" unconfirmed — possibly another Blupi-franchise title) |
00000038 |
SB2 v2.2 to BaH |
Version Tracking session (likely vs. "Blupi at Home", other/blupi_at_home/blupi-home-demo.exe) |
"New Traces" folder, IDs 00000004–00000034 (partial) |
Debugger trace captures dated 2023-03 and 2024-07, including traces of dplaysvr.exe and VirtualMIDISynth-synth.exe and several raw memory images (image00000000_00400000) |
Dynamic analysis sessions |
Not yet done, recommended before relying on this table further: open
each Program entry in Ghidra and record its analysis completeness
(has auto-analysis run? are functions named? is there a matching
ProgramUserData entry with meaningful annotations, or is it an empty
import?) in docs/variant-matrix.md per Section 9.
Seeded from the README's own classification (not independently
re-verified line-by-line in this pass — that is exactly the job of
docs/decomp-status/ going forward, see Section 9).
| File | Lines | README status | Notes |
|---|---|---|---|
src/event.cpp |
5,878 | Needs most work (#1 priority) | Largest file; input handling + WM_PHASE_* dispatch; 6 of the repo's 11 inline TODOs live here |
src/decblupi.cpp |
4,395 | Needs most work (#2) | Player physics, all vehicle logic |
src/pixmap.cpp |
1,922 | Tentatively complete | DirectDraw surface management |
src/decor.cpp |
2,044 | Needs most work (#8) | Core world init/render/step loop |
src/decmove.cpp |
2,311 | Needs most work (#6) | Enemy/moving-object AI |
src/blupi.cpp |
953 | Tentatively complete | WinMain, window/message loop |
src/sound.cpp |
779 | Tentatively complete | DirectSound backend |
src/soundbass.cpp |
720 | (BASS alt. backend, not separately classified in README) | Enabled via _BASS flag |
src/decblock.cpp |
804 | Needs most work (#5) | Crate/door/block interaction; has 2 explicit TODOs |
include/dectables.hpp / src/dectables.cpp |
238 / 691 | Data tables (not separately classified) | Static lookup tables |
src/decdesign.cpp |
662 | Needs most work (#4) | Level editor logic; has 1 explicit TODO |
src/button.cpp |
393 | Tentatively complete | UI buttons |
src/decio.cpp |
391 | Needs most work (#3) | Save/load .blp serialization |
src/misc.cpp |
358 | Needs most work (#9) | Misc helpers |
src/network.cpp |
356 | Tentatively complete | DirectPlay session/lobby |
src/ddutil.cpp |
344 | (Utility, not separately classified) | DirectDraw helpers |
src/text.cpp |
334 | (Not separately classified) | Text rendering |
src/movie.cpp |
301 | Tentatively complete | AVI/cutscene playback |
src/web_persistence.cpp |
317 | (New, Emscripten-only) | IndexedDB persistence, no Windows-original equivalent to verify against |
src/wave.cpp |
272 | Tentatively complete | WAV loading |
src/decnet.cpp |
467 | Needs most work (#7) | Network game sync; has 1 explicit TODO |
src/menu.cpp |
160 | Tentatively complete | Menu rendering |
src/jauge.cpp |
147 | Tentatively complete | Gauge/progress bars |
(Header-only files and very small utility files omitted from this table
for brevity; docs/decomp-status/ should still track them individually
once created.)