report2026-08-19
repo@9f4d2f6
origin@aae649f

Clean-room reconstruction · status & audit

JN Engine

A clean-room reimplementation of the Open Media Toolkit 2.x engine behind Jimmy Neutron: Boy Genius, built against a Direct3D 7 command stream captured off period hardware. This is where it stands after a three-day evidence audit of the process itself — and a follow-up session that settled what the audit could not.

Credit

awefan4524 is the project's main contributor and triager, and maintains the Hot Wheels: Mechanix and Operation Krabby Patty efforts. Their work on the OMT format and the AWE Games modding problem predates this project by years: the 3DSP parsers covering v0–v5 with endianness auto-detect and extra-pass-set support, the bidirectional Canvas RLE, and the independent decoder that settled the 32-bit canvas byte order and the 8-bit palette layout when this project had them wrong. Those parsers are vendored here with credit. The reverse-engineering wins are theirs; what this repository adds is catalog integration around them.

audit 2026-08-16 → 08-18 open questions 2026-08-19 findings 712 + 44 citation check 99.7%
§0summary

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.

QuestionVerdictResult
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.

§1current state

Where the project actually is

measured2026-08-19
at 9f4d2f6
476commits
36,383lines of C
208class specs
63python tools
35shipped levels
15linked oracles

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.
ledger208 rows
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.

§2the audit

What the audit did

methodevidence-only
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.

§3resolved — Q1

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.

Confirmed

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.

17defective rows
8.2%of 208 specs
16binary-confirmed
180instances denied
272properties ignored
10correctly unresolved

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.

figurethe flip
site@004416d4
FUN_00454f00 push  $0x4c455635 — the level‑5 class-id registrar
5VEL read forward — what the spec records
LEV5 read as a FourCC — the class id of level 5
Actually 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.

samplelargest first
full table in
06-openq.jsonl
ClassSpec saysActuallyInstancesPropsEngine binds
C3DRocknot resolved3ROK9919vt_resolver_inert
C3DMovingTargetnot resolved3TAR2225vt_shadow
C3DEyenot resolved3EYE1525vt_eye
C3DDarwinFish3DIN3FIS1227vt_creature
C3DYokCargonot resolved3YCA1125vt_cargo
C3DRocketFuelnot resolved3FUE714vt_prop
C3DSparrow5VEL3SPW125vt_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.

causeone function
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:

  1. 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 is C3DEye. Fourteen classes differ from their schema entry by case alone. The answer sits two lines away and is missed.
  2. A regex for a class-id immediate in decompiled text, which only fires when the decompiler happened to print one.
  3. 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.

§4resolved — Q2

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.

Contradicted

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.

adjudicated6 invariants
against source

The invariants, checked against the engine's own source

InvariantVerdictEvidence
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.

§5resolved — Q3–Q5

Dates, discs and loose ends

Confirmed

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.

Confirmed

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.

Confirmed

Q5five items

The loose ends

  • LiveThe retracted 94% is still in both entry-point documents — and confirmed present on the published branch, not just locally. A four-line documentation patch is drafted and deliberately not applied.
  • ClosedThe Operation Krabby Patty Ghidra project sitting on this machine is one unrecorded hour — created 2026-08-10, one binary imported, nothing exported, and no link to the maintained effort or to any ledger row here. Its engine is confirmed not the toolkit: the disc carries 19 PowerRender object files and zero toolkit fingerprints, so nothing in the OMT toolchain transfers to it.
  • ClosedThe 68 GB XP backup does not contain the game and never did — 164,913 listed paths, zero hits for any game artifact. It is a family PC image. The recovery guide's claim that it “very likely holds the original binaries” is wrong; they are elsewhere on the same drive.
  • ClosedThe planned tooling entry tracked as a project has no design document. Its entire basis is one sentence in one conversation.
  • NarrowedThe audit brief's own authorship is still unnamed, but pinned: it arrived as a downloaded file two minutes before the session that executed it. No exported conversation covers the window at all — the chat archive stops eleven days earlier.
§6open work

What needs fixing

Ordered by how likely a wrong claim is to reach a reader who will act on it — not by effort.

P0docs mislead
every new reader
P0Remove the retracted statistic

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

P0Qualify the canvas-id invariant

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

P1specs assert
what data denies
P1Fix the FourCC resolver, then regenerate

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

P1Put the checker in CI

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

P1Re-run validation on 29 specs

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.

P2explanations
are wrong
P2Correct two invariant explanations

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.

P2Flag the duplicate FourCC

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.

P3facts to
correct
P3Say which year is meant

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

P3Correct the recovery guide

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.

P3Drop a phantom class from the registrar scan

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.

—needs a
decision
OwnerFour things measurement cannot settle

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.

§7reproduce

How to check any of this

all underdecomp-audit/

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.

ArtifactWhat it holds
findings/06-openq.jsonl44 tagged findings, one JSON object per line, every claim cited
findings/06-open-questions.mdThe long form: one section per question, each ending in a verdict
DECOMP-HARNESS-AUDIT.mdThe original report, with this session appended as Appendix B
openq/q1_check2.pyThe spec checker — 208 specs against three generated sources
openq/q1_binconfirm.pyDisassembles each disputed registrar and matches its vtables
openq/pe.pyPE 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.