The five open questions, settled
Each was left unresolved by the August audit because the original binaries and the engine's own source were on a powered-off disk. Both are now in hand.
| Question | Verdict | Result |
|---|---|---|
| Q1 spec defect rate | Resolved | 17 of 208 specs (8.2%) carry a wrong FourCC. 16 confirmed against the binary. |
| Q2 software renderer | Resolved | The plan named the wrong class family. Six invariants adjudicated; two contradicted. |
| Q3 facts from the PEs | Resolved | Built 2001-09-30. Three OMT2.dll builds share one API. One sub-question still open. |
| Q4 Mechanix placement | Resolved | No .gam ships. The extraction was complete; placement is compiled in. |
| Q5 loose ends | Resolved | All five closed. A retracted statistic is still live on the published branch. |
Tags follow the project's own discipline and are never promoted: Confirmed observed in a file or binary · Reported a log or a person said so · Inferred reasoned, with a stated falsifier · Contradicted sources disagree.
Where the project actually is
at 9f4d2f6
What it genuinely does
The engine parses the original's asset containers end to end — 0MF2
canvases, .gam property bags, task tables, Granny meshes for the sequel —
and reimplements the gameplay layer in C. One codebase builds a native Linux game, a
WebAssembly demo, and a replay renderer that plays back the original's captured D3D7 command
stream verbatim. Level 1 of Boy Genius renders faithfully against that capture;
the sequel runs with 22 levels.
The parts that work best are the un-fakeable ones. A linkage-certificate gate refuses to mark a class linked without a green headless oracle proving the ported behaviour matches the recovered decompiled body. A review pass mutation-tested every certified oracle and all eight then-certified oracles went red under a native-side perturbation. CI runs on a fresh Ubuntu host with no secrets, no XP box and no GPU, and a deliberate-regression pull request was used to prove the gate goes red.
What it does not do — stated by the project itself
- No decompilation-to-source of
OMT2.dll. The engine is reimplemented, not ported. - No automated re-derivation of specs from the binary. Spec authoring is model work, reviewed as prose.
- No capture without the Windows XP machine and a person at the keyboard.
docs/decomp_ledger.csv
The number that frames everything below
All 208 class specs sit at status=spec. Confidence is
Medium 181 / Low 27 / High 0, and the count of specs that
have ever reached validated is zero. The linkage gate —
the one mechanism that cannot be talked past — covers 31 class-aspects, of which 15
are linked and 16 are explicitly linked-blocked.
So roughly 15 of 208 classes carry mechanically-proven behaviour. The rest is careful, reviewed, well-cited prose that has never been checked against anything.
What the audit did
sources2,239
The audit asked one question: where can a wrong claim enter this project, and what would catch it? It read repos, session transcripts, git history and on-disk artifacts across three machines, tagged every claim with its provenance, and refused to promote a tag because an earlier session sounded confident.
It produced 712 tagged findings, then re-opened every single cited file to check the citation resolved, the line range existed, and the claim's distinctive tokens actually appeared there. 377 confirmed claims re-checked, 376 verified, one downgraded — 99.7%. It also catalogued fifteen distinct ways an unverified claim can enter and survive, each with a real observed instance rather than a hypothetical.
The finding that is the whole audit in one story
In late May a session wrote a findings document with headline numbers — 94% high-tier reproduction, 119/122 mesh agreement, 139 static coverage. Re-running the validator shipped in the same commit produces 1/70, 3/122, 94. The prose never matched its own code's output. The operator caught it by reading a number that felt wrong, and a second session independently reproduced the catch and overwrote the file with honest figures.
That is the good half. The bad half is that the correction landed where the error was made and nowhere else. The fabricated figure is still live today in the two documents the project tells every new contributor and agent to read first — and, as this session confirmed, on the published branch as well as locally. Worse, the same sentence welds the hallucinated statistic to a parsing rule, and both were then promoted into a “do not relitigate these” invariant list. A rule forbidding re-examination was applied to a claim that had never been examined.
The through-line: nothing here was caught by a gate. The 94% was caught by a person reading. A wrong transform document was caught by someone re-reading raw disassembly. Guesswork in an implementation was caught by the operator playing the game. The gates that exist are excellent and cover ported behaviour. They do not cover written claims — and the written claims are where the project's knowledge actually lives.
The specs, measured
The audit found one spec asserting something its own project data contradicted, and could not say whether that was an outlier. It is not.
checkerq1_check2.py
corpus208 specs
vs3 in-repo sources
A mechanical checker now compares every spec's Identity block against three independent
sources the project already generates: the .gam property schema, the class-id
registrar scan, and the engine's own native binding table. Then, for every disagreement, the
registrar function is disassembled out of Neutron.exe and its installed vtable
addresses are matched against the spec's own.
Fifteen specs declare their FourCC “not resolved; not a .gam-placed
object” when the project's own generated data resolves it. Two state an outright
wrong one. Twelve of the seventeen describe classes that are placed in shipped
levels — 180 instances and 272 harvested properties described by their own spec as
absent — and twelve are simultaneously bound to a native vtable in the engine. Every
one of the seventeen also states, in its Validation section, that there was nothing to
cross-check.
site@004416d4
FUN_004415a0 installs all five of C3DSparrow's vtables and pushes
$0x33535057 at 0x004416d4 — bytes 57 50 53 33,
FourCC 3SPW.
One four-byte immediate, read the wrong way round. The generator's last-resort branch takes
whatever registrar sits nearest in memory, and level 5's happened to be nearest —
so a bird got a level's id. The spec then reasons from the error: “FourCC
5VEL has no rows in the corpus, so this object is not placed in any shipped
level.” 3SPW has a shipped instance with 25 properties.
full table in
06-openq.jsonl
| Class | Spec says | Actually | Instances | Props | Engine binds |
|---|---|---|---|---|---|
| C3DRock | not resolved | 3ROK | 99 | 19 | vt_resolver_inert |
| C3DMovingTarget | not resolved | 3TAR | 22 | 25 | vt_shadow |
| C3DEye | not resolved | 3EYE | 15 | 25 | vt_eye |
| C3DDarwinFish | 3DIN | 3FIS | 12 | 27 | vt_creature |
| C3DYokCargo | not resolved | 3YCA | 11 | 25 | vt_cargo |
| C3DRocketFuel | not resolved | 3FUE | 7 | 14 | vt_prop |
| C3DSparrow | 5VEL | 3SPW | 1 | 25 | vt_creature |
Seven of seventeen shown. Five more rows describe classes with zero shipped instances, where only the “not resolved” half of the claim is false.
gen_placeable_
specs.py:225
All seventeen come from one resolver
The generator resolves a class's FourCC in three branches, and each one leaks:
- A case-sensitive dictionary lookup, built only from the property schema.
The schema stores Ghidra's captured name in upper case —
C3DEYE— while the key isC3DEye. Fourteen classes differ from their schema entry by case alone. The answer sits two lines away and is missed. - A regex for a class-id immediate in decompiled text, which only fires when the decompiler happened to print one.
- A nearest-preceding-registrar-within-0x800 address heuristic with no identity check at all. This is what handed C3DDarwinFish the id belonging to C3DDino, and C3DSparrow the id belonging to level 5.
And a fourth mechanism is pure omission: the registrar loader reads only two columns of the
class-id scan — the FourCC and the function address. That file's
class-name column, which spells out C3DCorona(),
C3DGRILL, C3DPASSCARD, C3DSmokePuff() in plain text,
is never read.
Of the 49 specs that state a FourCC at all, only 21 could have come from the identity-checked branch. Sixteen of the remaining 28 turn out right anyway, eleven are corroborated by nothing in the repo, and one is wrong.
Stated fairly
Ten specs declare “not resolved” and nothing contradicts them — those are correct. No stated FourCC conflicts with a registrar source keyed on the spec's own class name. All 21 values from the identity-checked branch are right. Two findings even run the other way: in one case the spec is right and the generated data names a class that does not exist in the recovered hierarchy at all.
A second, softer count: 29 specs (13.9%) state “no .gam properties to
cross-check” while the schema held 627 property rows across 617 shipped instances for
their FourCC. The narrow reading may be true; the sentence still declares validation
impossible when the data was sitting in a generated file in the same directory.
The renderer cross-check, finally run
Proposed 2026-06-07, written into the plan as high-value, never executed. It turns out the plan named the wrong thing.
premiseplan §10
The plan proposed decompiling “the OMediaWin* CPU rasterisation path”
in OMT2.dll as a second ground truth for rendering questions.
There is no such path. The thirteen OMediaWin* classes are the
Win32 platform layer — window, input, keyboard, sound, video engine, file and memory
helpers. There is no OMediaWinRenderer, OMediaWinRenderPort, or
OMediaWinCanvas. Anyone executing the plan as written would have spent the effort
decompiling windowing and sound.
The software renderer is the OMediaOMT* family, sitting over
OMediaPipeline and the segment rasterizers. And the shipped exe pair gives up
nothing: Neutron.exe and NeutronSW.exe import
byte-identical sets from all four DLLs and have identical string
sets — 3,416 each, zero difference either way. There is no import-table
boundary to isolate. Renderer selection happens inside the DLL, at a single factory call,
switching on an engine id between the DirectX renderer and the software one.
A note on a number the audit carried: the “52% of bytes differ” figure between the two exes is real, but it measures code shifting, not code change. The text sections differ by 16 bytes in size and the delta shows up as 41,846 runs, mostly one byte long.
The better answer was already on disk. The recovered GarageCube LGPL 2.5.0 drop carries full C++ source for both backends — 4,900 lines across the pipeline, the software renderer and the DirectX renderer. Nothing needed decompiling.
against source
The invariants, checked against the engine's own source
| Invariant | Verdict | Evidence |
|---|---|---|
| Column-major / column-vector | Confirmed | Header says “same as OpenGL”; multiply is m[col][row] on a column vector |
| PROJ[3][3]=1 is real | Confirmed | All four perspective builders write it; set_projection raw-casts to D3D unmodified |
| …and it is a “w-buffer” | Contradicted | ZENABLE is only ever TRUE or FALSE, never USEW; nothing references w-buffering |
| Zero engine-side UV flips | Confirmed | Coordinates copied verbatim at both draw paths; no inversion anywhere in the tree |
| The capture has no fog | Confirmed + | All four fog methods are empty stubs; the material flag is marked “not implemented” |
| CULLMODE=NONE because the pipeline software-culls | Contradicted | Only the software renderer uses that pipeline; the DirectX path never touches it |
Two results are worth more than a checkmark. The fog invariant gets stronger: it is not a property of one capture, it is a property of the engine — fog was never implemented on the Windows path, so adding fog to “fix edges” would be adding something the original never had.
And the culling explanation is simply wrong. OMediaPipeline is referenced by the
software renderer and by nothing else, so it cannot explain what a Direct3D 7 capture
recorded. In the DirectX path culling starts on and is toggled by a two-sided flag
plus an unconditional disable in the canvas-element render path. The observation
stands and the practical fix stands — only the reason written next to it is wrong.
One genuine backend divergence surfaced, offered as
Inferred with its falsifier: the projection fast path never
reads m[3][3], so the software pipeline computes w = z while D3D,
handed the same matrix, computes w = z + 1. Negligible at gameplay depths, which
is presumably why nobody noticed in twenty-four years.
Dates, discs and loose ends
Q3PE headers
The binaries date themselves
Reading PE timestamps directly settles a three-way disagreement in the docs.
Neutron.exe was linked 2001-09-30 01:07:40 UTC;
NeutronSW.exe seventy-nine seconds later, from the same object-file population
— the Rich header component counts are identical, which is what a compile-time variant
of one source tree looks like. The toolchain is MSVC 6.0 RTM, reproduced independently of the
earlier session that claimed it.
This dates the build. A PE header cannot settle a retail street date, so the docs' “THQ, 2002” is not necessarily wrong — but three documents say 2002 without saying which they mean, and the stray “2003” elsewhere belongs to the toolkit's open-source release, not the game.
The three shipped OMT2.dll builds — one per title, spanning thirteen
months — export the same 1,358 names in the same ordinal order, with
zero ordinals carrying a different name in any pairwise comparison, while only 23, 6 and 5 of
1,358 export addresses coincide. One API, three builds of an evolving tree. They are not
interchangeable: two import DINPUT8, the third imports DINPUT.
The one sub-question still open
A note in the project instructions says a 2.5.0 SDK drop-in “fails on a struct size
mismatch at offset 0x140”, naming no class and no build log. Both halves
of the old contradiction now have support: all 180 name-parseable imports resolve against
the 2.5.0 source, and the API genuinely drifted — the 2001 DLL exports fog
methods and two whole classes that 2.5.0 does not have. The failure mechanism is real too:
the game bakes 100 distinct object sizes into 313 allocation sites. But
0x140 is not among them. Settling it needs the original build
error text, or the class the operator had in view.
Q4disc manifest
Hot Wheels Mechanix ships no placement data
Work on this sibling title — another of awefan's maintained efforts, with its own RE
workbench — was paused in June on the theory that its missing .gam files
were an extraction artifact. They are not. The disc's
uncompressed installer header lists exactly 44 files — 36
asset containers, two executables, three DLLs, two small data files and a readme. A raw scan
of the entire 91 MB disc image finds zero .gam filenames
while finding 36 .omt and 294 .png references.
The manifest and the already-extracted install tree match 44 for 44. The
June extraction was complete; re-extracting could only reproduce the same files. Placement is
compiled: of the 32 call sites of the game's class factory, 29 pass a literal
four-character id as an immediate. The .gam toolchain does not transfer
to this title because there is nothing for it to parse.
Q5five items
The loose ends
What needs fixing
Ordered by how likely a wrong claim is to reach a reader who will act on it — not by effort.
every new reader
Two documents still print a fabricated ~94% figure that was corrected at source in May. Both are the files the project tells every new contributor and agent to read first, and both carry it on the published branch.
ARCHITECTURE.md:290 · PROJECT_HISTORY.md:257 · patch drafted, four lines
The rule holds for the heuristic-scanned parsing path only; the header path yields the id directly. Both invariant lists state it unconditionally, inside a section that forbids re-examination.
ARCHITECTURE.md:420 · PROJECT_HISTORY.md:1246 · same patch
what data denies
Three changes close all seventeen defects at the source: match class names case-insensitively, read the class-name column of the registrar scan, and either drop the address-proximity fallback or make it verify the vtable it claims. Regenerating afterwards is cheaper and safer than editing seventeen specs by hand.
tools/gen_placeable_specs.py:225–241
This is the structural fix, and the reason the measurement was worth doing. Nothing in the
pipeline currently cross-checks a spec against the project's own generated data. The checker
written for this session runs in seconds over all 208 specs and needs no binary. Wiring it
into make check converts the largest gap in the audit from a standing risk into
a gate.
decomp-audit/openq/q1_check2.py → tools/ + CI
Each declares there were no properties to cross-check while 627 harvested property rows existed for its FourCC. The property tables were free; only the consuming logic ever needed recovering.
are wrong
Drop “w-buffer” from the projection invariant — the value is real, the depth mode is a plain z-buffer — and replace the culling explanation, which attributes a Direct3D capture to a pipeline the DirectX renderer never uses. Neither change affects any ported behaviour; both prevent someone reasoning correctly from a false premise.
One four-character id has two registrars, exactly the shape the schema already flags a caveat for elsewhere. Its 22 shipped instances are tagged as one class while the engine binds the id to the other. Worth resolving before anyone ports that behaviour.
correct
Three documents say 2002 without distinguishing build from release; the binaries were linked 2001-09-30, and the stray 2003 belongs to the toolkit's open-source drop.
README.md:4 · ARCHITECTURE.md:21 · PROJECT_HISTORY.md:16
It points at a 68 GB tarball for the original binaries. They are not in it; they are in the extracted install tree on the same drive. This is the first file the next person is told to read.
One generated row names a class that does not exist in the recovered hierarchy — the column is documented as “class or nearby string”, and here it caught a nearby string.
decision
The 0x140 mismatch needs the original build error text or the class name
behind it. The audit brief's authorship needs the missing conversations exported. The local
Krabby Patty Ghidra project needs connecting to the maintained effort — or
retiring, so it stops looking like untracked work. And the planned tooling entry has no
design document; someone needs to say whether it is a real plan or a passing remark the
ledger promoted into a project.
How to check any of this
Every claim on this page resolves to a file and a line, or to an offset in a binary. The
tooling is re-runnable and needs no network, no Ghidra and no XP machine — a
dependency-free PE reader plus objdump covers all of it.
| Artifact | What it holds |
|---|---|
| findings/06-openq.jsonl | 44 tagged findings, one JSON object per line, every claim cited |
| findings/06-open-questions.md | The long form: one section per question, each ending in a verdict |
| DECOMP-HARNESS-AUDIT.md | The original report, with this session appended as Appendix B |
| openq/q1_check2.py | The spec checker — 208 specs against three generated sources |
| openq/q1_binconfirm.py | Disassembles each disputed registrar and matches its vtables |
| openq/pe.py | PE headers, Rich header, imports and exports, no dependencies |
Nothing was edited to produce this report. No spec was corrected, no document was patched, and the disc image was not re-extracted — the uncompressed manifest already settled the question and a re-extraction could only reproduce the same 44 files. Said plainly so it is not mistaken for coverage.