OVERDOSE PROTOCOL
Total Overdose — Reverse Engineering Project
ACTIVE MILESTONE
Full RoadmapPhase 2 - Rendering Pipeline
DirectX 9 fixed-function pipeline. DONE: RenderParticle, RenderTexturedQuad_3 (5-mode billboard), GfxInternal_Dx9 compiles and links. Remaining: real DDS texture loader (currently stubbed).
LATEST TRANSMISSION
All LogsMILESTONE: KapowEngineEditor.exe builds
The standalone Kapow Engine Editor binary now builds end-to-end. 2.13 MB PE32 i386 GUI executable, separate from the dinput8.dll injection used to boot the retail game. This is the foundation of the Editor - our actual end-goal tool.
TASK BOARD
Q72: LoadNativeResource placeholder fallback shipped (cf2d4e3)
Defensive guard: when resdir is non-empty (e.g. texture_pc), construct placeholder Asset with HasPlaceHolder flag instead of returning null. Independent path to first pixels alongside Sonnet's Q70/Q71. Source: AI_BRIDGE.md, parallel-assignment cadence per Sandy directive 'aim for massive win'.
Q80-Q100: M7/M8/M9/M10 next-wave backlog (Sonnet+Gemini)
Animation system (Q80-85), texture decoder (Q86-90), audio pipeline (Q91-95), physics integration (Q96-100). Drafted as immediate post-M5-first-pixels work. See AI_BRIDGE.md Opus 4.8 entry at 09:48. Source: AI_BRIDGE.md, parallel-assignment cadence per Sandy directive 'aim for massive win'.
Q106 SHIPPED: Instantiate guard softened for THREE-type assets
Commit 1a2c292. When asset type isn't resolved upstream but assetPtr's vtable points into a sane code-section range (0x00400000-0x7FFE0000) AND AssetType==THREE, dispatch ApplyAssetData anyway. Unblocks Q86 checkerboard chain. Pending harness verification (env wobble this session). Source: AI_BRIDGE.md, parallel-cadence session.
Engine state milestone: 60fps stable 900 frames with scene loaded
Engine successfully boots, loads OverdoseIntro.scene, walks 102 entities in recursive scene-tree walker, Sprite::Render fires for 50+ sprites/frame. Q72 LoadNativeResource placeholder confirmed firing. 33 Sprites now have valid m_AssetPtr (up from 0). 14 FindLoadedAsset matches (up from 0). Last remaining gate: m_Texture_1 population - Q106 should fix once verified. Source: AI_BRIDGE.md, parallel-cadence session.
Q111: harness env recovery (fresh shell investigation)
Multiple harness attempts show 'TOD never spawned' even after killing TOD/cdb processes. cdb log shows DINPUT8.dll loads with full Debug CRT chain but TOD process termination/spawn detection fails. Possibly stale OS resource. Workaround: run from fresh PowerShell or reboot. Investigation task. Source: AI_BRIDGE.md, parallel-cadence session.
TR-2: Verify Sprite::Render emits DrawPrimitive (validation of abfa74d)
Assignee: Sonnet Sonnet's commit abfa74d shipped Sprite::Render full implementation. Run harness, look for 'DIAG Sprite::Render entry 1:' trace lines. Verify Sprites with valid m_Texture references actually emit DrawPrimitive. Report DrawIdx/frame counts. Source: AI_BRIDGE.md update at 2026-06-24 08:18 ('KabutoKun Discord intel + state ack').
[ASSET PARSE] Recursive Instantiate produces garbage index (>1.9B) - pointer-advance off
After fixing FragmentAsset crash, subsequent Instantiate calls get assetTypeIndex=1900414856 (and similar billions) -- pointer arithmetic in ParseAssetData/the parser is off by N*sizeof(record). We survive only because of the bounds check. Real fix: align our parser to the original asset DB layout. Related to #39 (checksum drift).
[ASSET] CompiledAssetInfo::_85E160 is a Ghidra stub - needs proper reimpl
FragmentAsset::ApplyAssetData line 81 calls _85E160 which (in AssetManager.cpp:1613) is a partial Ghidra decompile with TODO branches; the advance overshoots and leaves assetInfoPtr=0xFFFFFFE0. Need to disassemble TOD.exe at 0x85E160, study original advance logic, complete impl. Until then FragmentAsset::ApplyAssetData stays stubbed (issue #38).
[GFX] DEV_FORCE_WINDOWED produced 0x0 invisible window - needs SetupWindowedRenderer fix
## Symptom Defining `DEV_FORCE_WINDOWED` in `KapowEngineClass.cpp` to force `SetupWindowedRenderer` instead of `SetupFullScreenRenderer` caused TOD.exe to create a window with client area 0x0 (invisible). Reverted in commit `c78c1e8`. ## Why we wanted it Original 800x600 fullscreen often fails on modern displays. Windowed is more permissive. ## Root cause hypothesis `SetupWindowedRenderer(m_DisplayModeResolution)` was called with `m_DisplayModeResolution` not yet populated ? `(0,0)`. Need to use a sensible default (e.g. `{800, 600}`) or read `m_ViewportResolution` instead. ## Status note Since we now run via `Direct3DCreate9Ex` + `Windowed=TRUE` set at `CreateRenderDevice` time (in commit `1ff681b`), this issue is partially obsolete - D3D opens windowed and gets a real `hWnd`. But the dedicated DEV_FORCE_WINDOWED path is still broken if anyone re-enables it.
[CLEANUP] Remove DIAG noise (ListType ctor, DataType ctor, PP.* dump) once boot is stable
## What While debugging the boot crash we added ~10 `LogDump::LogA("DIAG ...")` lines in: - `TOD1/ListType.cpp` (BEGIN/MID/STEP1/STEP2/END for every ctor - fires ~50 times) - `TOD1/DataType.cpp` (every ctor) - `TOD1/GfxInternal_Dx9.cpp` (PresentParameters dump, Direct3DCreate9Ex result, 3-tier device fallback chain log) - `TOD1/KapowEngineClass.cpp` (LoadScripts MILESTONE) - `TOD1/Texture.cpp` (CreateDirect3DTexture diag) ## Why keep them now They're load-bearing for the next round of debugging (Retail Assets table). Once boot reaches the main loop, demote them to `#ifdef DIAG_BOOT_FLOW` or delete. ## Action 1. Wait until Retail Assets blocker is resolved 2. Delete or gate the DIAG lines 3. Keep the `DIAG: PP.*` dump permanently (rare but valuable for future driver-compat issues)
[RECON] Replace _reconPad_* alignment fields with real members (~62 classes)
## Context ASSERT_CLASS_SIZE enforces ABI compatibility with TOD.exe. ~62 reconstructed classes still have placeholder `_reconPad_*` byte arrays to hit the asserted size. Notable: - `Scene` (+32 bytes pad) - `EntityType` (+16 bytes pad) - `Texture` (verified clean) - `GfxInternal_Dx9` (large pad mid-class) ## Why this matters now The Properties/Commands checksum mismatch may partially originate here - if a reconstructed property's offset is wrong because surrounding pads are wrong size, the CRC of the property table will differ from the original. ## Action Tracked per-class. Open sub-issues as each is tackled. Reference original Ghidra struct vs reconstruction.
[EDITOR] --editor cmdline switch missing on KapowEngineEditor.exe
## What `KapowEngineEditor.exe` (the standalone Release EXE) builds cleanly (commit `1a2cdda`) but currently launches in game mode like `TOD.exe` would. We need a `--editor` cmdline flag that: 1. Opens the engine's Editor UI (the File/Edit/Functions debug menu we saw briefly during the early boot test) 2. Skips the splash/intro 3. Loads `OverdoseIntro.scene` directly into the editor ## Where to add `InitialiseGame(char* cmdline)` already receives the cmdline. Parse `--editor` / `-e` there and set a global flag the editor mode reads in `KapowEngineClass::Init`. ## Blocked by The Retail Assets table issue - editor needs assets too. Pick this up once the engine reaches main-loop.
Editor: wire up file menu / scene browser
Once boot works, hook up: New Scene, Open Scene, Save Scene Session (the EDITOR_SESSION_FILE_VERSION=3 format), editor camera controls (mouse/wasd), node tree panel.
First boot: launch the reconstructed binary
Once linking, drop dinput8.dll into game install and attempt boot. Expect crashes; debug boot path.
Reverse Scene::LoadMap (FUN_00890000, asset block loading)
Map-chunk loader - critical EXE path. Loads .naz/.main blocks into the scene via MAIN_ASSETS/MISSION_ASSETS allocators.
Assemble Scene::Update (FUN_00897450) from reversed helpers
BLOCKED on the 10 leaf helpers. Once all are reconstructed and declared, assemble the full 3,159-byte function using the verified Scene.h symbol table. Mechanical once deps exist.
Build .naz / .main asset format parser
Open-source parser for the Kapow engine asset block format. Required for standalone EXE asset loading.
Reverse remaining 268 engine stubs
Epic: 268 TODO_IMPLEMENTATION markers remain across 73 source files. Prioritized by critical EXE path (Scene, AssetManager, PhysSystem first).
[BOOT] Engine 'Not Responding' after main-loop entry - msg pump not running?
Window opens with correct title ('Total Overdose ** ( scripts: /data/scripts/stable/)'), 243 MB memory, but Windows shows 'Not Responding' - message queue not pumped. Could be: (a) genuinely hung in init, (b) blocked on missing asset, (c) message pump not implemented in Platform::Process. cdb non-invasive attach + ~* kpn will tell us where the threads are.
[ASSET] Properties/Commands checksum mismatch (asset DB vs recompiled engine)
## Symptom At `AssetManager.cpp:600,608` the engine compares two CRC32 fingerprints (Properties table + Commands table) against the values stored in the asset .main file. Currently: | Field | Asset has | Engine has | |-------|-----------|------------| | Properties checksum | `0x65c37710` | `0x6e6b6da6` | | Commands checksum | `0xe5ae7722` | `0xaf5d3dab` | Validation already bypassed under `INCLUDE_FIXES` (logs instead of returning null). Without the bypass, asset loading hard-fails. ## Why this matters The mismatch tells us our reconstructed `DataType` properties and `Command` tables don't match the 2005 game's exactly. With the bypass, downstream code may parse `CompiledAssetInfo` with the wrong offsets ? silent corruption. ## Two paths - **(A) keep bypass**, treat mismatch as informational, fix concretely on a case-by-case basis as downstream bugs surface - **(B) brute-force match**: enumerate every Property name/type that contributes to the CRC, diff against the original, fill missing entries until checksums agree (A) is the current pragmatic choice. (B) needs Ghidra archaeology of `GetGlobalPropertyListChecksum` / `GetGlobalCommandListChecksum`. ## Refs - `TOD1/AssetManager.cpp:590-612` - INCLUDE_FIXES guard around both `return nullptr;` lines
[D3D9] Review D3DPOOL choice for vertex/index buffers after D3D9Ex migration
## Context D3D9Ex devices don't support `D3DPOOL_MANAGED`. Commit `169085f` swapped all 4 sites: - `Texture::CreateDirect3DTexture` non-RT path ? `D3DPOOL_SYSTEMMEM` - `GfxInternal_Dx9::CreateVertexBuffer` ? `D3DPOOL_DEFAULT` - `GfxInternal_Dx9::CreateIndexBuffer` ? `D3DPOOL_DEFAULT` - `GfxInternal_Dx9::CreateTexture` helper ? `D3DPOOL_SYSTEMMEM` ## Risk `D3DPOOL_DEFAULT` vertex/index buffers **cannot be Lock'd** unless created with `D3DUSAGE_DYNAMIC`. If the engine later calls `Lock()` on a `DEFAULT` VB/IB without that flag, it returns `D3DERR_INVALIDCALL` (we'll see "Error - could not lock" again). ## Action 1. Audit every `VertexBuffer::SetData`/`IndexBuffer::SetData`/`Lock` call path 2. Add `D3DUSAGE_DYNAMIC` to the `usage` arg in `CreateVertexBuffer`/`CreateIndexBuffer` when the original used `MANAGED` and the buffer is mutable 3. For static, write-once buffers, leave as `DEFAULT` (faster) and use `UpdateTexture`/`UpdateSurface` instead of `Lock` ## When to fix Only after we get past the Retail Assets blocker - won't hit this code path until rendering starts. ## Refs - `TOD1/GfxInternal_Dx9.cpp` line ~1640 - Commit `169085f`
Editor: launch KapowEngineEditor.exe directly into editor mode
Add cmdline flag (--editor or similar) to InitialiseGame so the standalone binary boots into editor mode. Currently it just runs the same game-mode init path.
Reverse FUN_00892f50 (per-child script trigger)
Triggers active scripts on child entities in the Scene::Update script-dispatch tail (m_ScriptSlots[1] & 0x20).
Reverse FUN_00893a50 (scene helper)
Helper called in the rewind-commit branch of Scene::Update.
Reverse FUN_008aae20 (rewind transaction commit)
Commits per-frame entity property transactions into the rewind buffers when in gameplay mode.
Reverse FUN_0089c050 (sound-emitter update helper)
Iterates the sound-emitter list in Scene::Update's tail.
Reverse FUN_0088e1e0 (node/child rewind helper)
Per-child helper invoked during the MODE_STOP rewind path of Scene::Update.
Reverse FUN_00896180 (scene update tail helper)
Part of the Scene::Update entity-update tail (MODE!=STOP branch).
Reverse FUN_00893b30 (post-flag scene update step)
Called immediately after the camera/rewind flag block in Scene::Update.
Reverse FUN_00894bf0 (FlushRewind implementation)
Handles the m_FlushRewindRequested flag in Scene::Update - flushes the rewind transaction buffers.
Reverse FUN_00892b10 (Scene::_892B10 sub-update)
Scene method called on SceneInstance in both the init-mode and main-update paths of Scene::Update. Blocker #2.
Asset loader: diagnose 'asset has 0' checksum bug
Engine boots and tries to load resource block 0; FileBuffer reads all zeros from the asset file. Could be wrong path, wrong file association in .naz archives, or FileBuffer not finding the .naz entry. Diagnostic LogDump lines added - need a fresh boot trace.
Apply GfxInternal_Dx9 stubs to source
Port implemented stubs from GfxInternal_Dx9_stubs.cpp into GfxInternal_Dx9.cpp per APPLY_GUIDE.txt.
Implement Scene::Update (FUN_00897450)
3159-byte per-frame state machine: delta time, save/restore, rewind flags, m_State dispatch. Decompile at decompiled_output.txt:482858. Handed to Gemini via AI_BRIDGE.md.
Q70/Q71: blocks.naz stride parser fix (Sonnet, critical-path)
Sentinel-anchored record parser using 0xBAADF00D + 0xABABABAB MSVC alloc-fill patterns as boundaries. Hex evidence in docs/BLOCKS_NAZ_FIRST_4K_HEX.txt. Records start every ~192 bytes with twin-string pattern (camelCase + lowercase alias). Goal: registry goes from 8 garbage entries to 500+ valid ones, unblocks DrawIdx > 0 -> FIRST PIXELS. Source: AI_BRIDGE.md, parallel-assignment cadence per Sandy directive 'aim for massive win'.
TG-1: Auto-call Scene::Start at end of OpenScene + F5 shortcut
Assignee: Gemini Per Sonnet analysis (DIAG bridge entry): editor's Scene::Start is skipped (m_ShouldStartGame=false), so m_PlayMode stays MODE_STOP, scripts never fire, MoviePlayer::Play() never called, DrawIdx stays 0. Options: (a) Auto-call Scene::Start at end of OpenScene under INCLUDE_FIXES; (b) F5 keyboard shortcut to Scene::Start; (c) both. Source: AI_BRIDGE.md update at 2026-06-24 08:18 ('KabutoKun Discord intel + state ack').
TG-2: Verify Mode panel STOP/PLAY actually reach Scene::Start
Assignee: Gemini Gemini's prior commit (Task A) wired WM_COMMAND for Mode panel control ID 1045 PLAY. Verify it reaches Scene::Start. Run TOD interactively, click Play after Open Demo Scene, check trace for Scene::Start log. If broken, fix. Source: AI_BRIDGE.md update at 2026-06-24 08:18 ('KabutoKun Discord intel + state ack').
[BOOT BLOCKER] Hidden VC++ Runtime Library dialog after main-loop entry
## What we know After capping `GetNextLoadedAsset` (issue #46 workaround), engine now: - Returns from `KapowEngineClass::Init` - Window status flips `Not Responding` -> `Running` (243 MB -> 470 MB) - ~160 more trace lines fire (5065 -> 5229) But then a **hidden** modal dialog appears: ``` hwnd=1315520 visible=False parent=3610014 (main window) class=#32770 (dialog) title='Microsoft Visual C++ Runtime Library' ``` The dialog is *owned* by the main game window so it sits behind it - user sees nothing on screen, just a blank/unresponsive main window. `SetForegroundWindow` couldn't pull it forward (probably because main window owns the modal stack). ## Stack at the time (cdb non-invasive attach) ``` 00 win32u!NtUserWaitMessage+0xc 01 USER32!DialogBox2+0x1ba 02 USER32!InternalDialogBox+0x11c 03 USER32!SoftModalMessageBox+0x6f3 04 USER32!MessageBoxWorker+0x309 05 USER32!MessageBoxTimeoutW+0x187 06 USER32!MessageBoxTimeoutA+0xae 07 USER32!MessageBoxA+0x45 08 TOD+0x561caa <- caller in original TOD.exe 09 TOD+0x55c687 ``` ## Most likely cause Last log before silence: `DIAG Instantiate: ParseAssetData done` for asset index 1,900,419,068 (garbage). Next line in our source is: `cpp if (assetBuffer->AssetType == CompiledAssetInfo::tAssetType::THREE) assetPtr->m_GlobalResourceId = g_AssetManager->AddAssetReference(assetPtr); ` `assetPtr` has a garbage vtable (`0x714617FC`) because no AssetInstance resolved. `AddAssetReference` probably trips `abort()` / pure-virtual / `_invalid_parameter` from inside the STL when handling this garbage pointer. ## Next steps 1. `LogDump::LogA` flushed *immediately before* `AddAssetReference` to narrow the trigger 2. cdb attach earlier, set breakpoint on `MessageBoxA` and `abort`, dump caller before dialog shows 3. Or: skip `AddAssetReference` when `resolved == nullptr` (treat as junk asset, don't register) ## Related - Issue #46 (recursive Instantiate produces garbage indices) - root cause - Issue #47 (`_85E160` stub) - same upstream parsing bug
[BOOT BLOCKER] ApplyAssetData crashes on asset type index 4 (vtbl=0x71460748)
## ?? Asset loading complete - engine now reaches main loop ### Resolution path 1. Retail Assets table was NEVER the problem (verified DIAG: retailSize=11, ourSize=11, AssetInstance for 'fragment' resolved cleanly). 2. Real crash was in `FragmentAsset::ApplyAssetData` - its line 81 call to `CompiledAssetInfo::_85E160` (a Ghidra-decompiled stub) overshoots the data stream and overwrites `assetInfoPtr` to `0xFFFFFFE0`. Next line tries to deref ? AV. 3. Stubbed `FragmentAsset::ApplyAssetData` to early-return under INCLUDE_FIXES. Other 14 assets load fine. ### New state - `Done. Loading 15 resource took 0.15 secs.` - Engine runs 17+ seconds with no further trace output - **main loop reached** - 5065 trace lines (up from 4878) ### Follow-ups (will be filed separately) - `FragmentAsset::ApplyAssetData` proper implementation - re-implement `_85E160` based on Ghidra disasm at original address `0x85E160` (issue #39 territory - same code path uses the type info) - The recursive Instantiate is currently producing `idx=1900414856` (garbage out-of-bounds index) because ParseAssetData's pointer-advance is also off - we survive only because we now log+return on bad indices. Real fix wants the index decode to match the original parser. Marking this issue as Done (the Retail Assets misdiagnosis is resolved).
?? D3D9Ex breakthrough - 6x boot progress (Win11/AMD compat)
## ?? BREAKTHROUGH (2026-06-22 overnight session) After 20+ hours of investigation, TOD.exe boot progression went from **800 trace lines (crash)** to **4,871 trace lines (graceful exit at asset instantiation)**. ### Root cause (found via WinDbg/cdb) The original boot crash was `__fastfail(5) FAST_FAIL_INVALID_ARG` triggered by stack-cookie corruption in `Texture::CreateDirect3DTexture` epilogue. Real bug: `m_Direct3DDevice` was NULL because the upstream `CreateDevice` failed silently with `D3DERR_DEVICELOST` (0x88760868). Three device tiers (HARDWARE/SOFTWARE/REF) ALL returned the same error - REF doesn't use the GPU, so this ruled out driver/adapter issues. Cause: **Win11 + AMD WDDM driver no longer accepts classic `Direct3DCreate9`**. ### Fix (commits `1ff681b`, `169085f` on `gitlab/main`) 1. Added a `Direct3DCreate9Ex(D3D_SDK_VERSION, &d3dEx)` call after the classic `Direct3DCreate9`. `IDirect3D9Ex` inherits from `IDirect3D9` so the rest of the engine uses it transparently. 2. `D3DPOOL_MANAGED` ? `D3DPOOL_DEFAULT`/`SYSTEMMEM` everywhere (Ex devices don't support MANAGED): - Textures (lockable path) ? SYSTEMMEM - Render-target textures ? DEFAULT - Vertex/Index buffers ? DEFAULT ### Now passing - ? D3D9Ex device created - ? Graphics init complete (no errors) - ? All 5 NAZ archives opened (blocks, sounds, uk_sounds, videos00, videos01) - ? Script database loaded - ? Resource block loaded - ? `OverdoseIntro.main` (413,476 bytes) opened - ? CompiledAssetInfo created ### Open issues from this session - #38 [CRITICAL] Retail Assets static registry empty at LoadResourceBlock - #39 [CRITICAL] Properties/Commands checksum mismatch (asset DB vs engine) - #40 [MEDIUM] D3DPOOL choice review (DYNAMIC usage flag for VB/IB) - #41 [MEDIUM] DEV_FORCE_WINDOWED produced 0x0 window - #42 [LOW] Remove DIAG noise once boot stable - #43 [MEDIUM] Replace _reconPad_* fields (62 classes) - #44 [MEDIUM] --editor cmdline switch missing ### Tooling added - `C:\tod2026\debug-hang.ps1` - automated cdb harness for live call stacks - `C:\tod2026\cdb-script.txt` - reusable cdb command sequence - `DIAG:` instrumentation throughout boot path (gated under INCLUDE_FIXES) Engine source: `gitlab/main` HEAD = commit `1ff681b`
?? Standalone KapowEngineEditor.exe builds
Release EXE config fixed (DynamicLibrary -> Application); renamed TargetName to KapowEngineEditor. 2.13 MB PE32 i386 GUI binary. Commit 1a2cdda.
Linker pass: resolve unresolved externals
After C2xxx -> 0, expect a wave of LNK2001/LNK2019 unresolved externals. Reverse / stub the missing functions until link succeeds.
Fix remaining 40 build errors (C2065/C2248/C2039)
22 undeclared identifiers, 13 access violations, 4 not-a-member, 1 identifier-not-found. Final compile push -> linker stage.
Fix 159 implementation errors (missing decls + access fixes)
Gemini cleared 159 errors across multiple files. Commit 82f1615.
Struct sizes: pad all 62 undersized classes to expected size
Build-verified actual vs expected sizes for all 62 classes; padded with char _reconPad[N] tail members. Commits c303d80, 563dff1, 9a19174. C2338 -> 0.
Fix SystemSubAllocator aligned-malloc inversion
Backwards #ifdef + wrong _aligned_offset_malloc_dbg args. Commit d3f39d3.
Config: add ThirdParty include + /vmg + DIRECTX defines to Release
Three vcxproj config gaps fixed: ThirdParty on include path (-125 errors), /vmg member-pointer ABI on Release (-2064 errors), DIRECTX;PLATFORM_PC on Release (-152 errors). Commits a841b66, 0d320ef, 3747138.
Set up VS Build Tools 2022 + DirectX SDK
Toolchain installed: VS Build Tools 2022 (v143 + MSVC 14.44), DirectX SDK June 2010, DXSDK_DIR set. Build script build.ps1 written and committed.
BestFitAllocator - fully implemented
All stubs implemented and verified against Ghidra decompile: - `_478F70` free-list insertion - `PlacementNewAligned` (FUN_00479350) - alignment bug fixed (header offset), missing stats added - `Realloc` (FUN_004795C0) - shrink-in-place, adjacent merge, fallback alloc+copy+free - `TryExpandBy` (FUN_00479490) - speculative in-place expansion via merge loop Commit: de3cc42 on kapow-systems-3d-2026-project
Fix GetAvailableMemory bucket off-by-one
Existing loop uses i=20..1 (accesses field_24[20] OOB, skips field_24[0]). Should be i=19..0 like the verified GetBiggestUsedMemoryBlock walk.
Implement BestFitAllocator::_478910 (free-list rebalancer)
327-byte self-contained bucket load-balancer: migrates blocks between adjacent free-list buckets when a neighbour has <= half the count. No external deps - Claude implementing, Gemini to cross-check.
Reverse FUN_00951640 (frame-time ms helper)
Leaf dependency of Scene::Update. Provides the millisecond delta used to accumulate Scene::RealTimeMs. Smallest of the 10 blockers - start here.
Implement RenderParticle (FUN_00454d70)
23,593-byte particle render function fully implemented and verified against Ghidra decompile.
Implement RenderTexturedQuad_3 billboard path
Full 5-mode billboard system: camera-facing, H/V-constrained, look-aligned, pass-through. Matrix math confirmed via binary analysis (matWorld = matBillboard x matTranslation).
Reconstruct MemoryManager arena system
10-arena fixed-pool memory model reconstructed: DEFAULT, MAIN_ASSETS, MISSION_ASSETS, CUTSCENE_OR_REWIND, PLAYER_DATA, TEMP, RENDERLIST, SCRATCHPAD, COLLISION_CACHE_ENTRIES, DEFRAGMENTING.
Implement Scene::ReleaseQuadTreeAndRenderlist (FUN_00896C30)
Quadtree + render-list teardown. Stub has partial logic; needs _895F50 internals and ClearNodesLists verification from Ghidra.
Implement BestFitAllocator::GetBiggestUsedMemoryBlock + identify stub27/stub29
GetBiggestUsedMemoryBlock returns NULL; stub27/stub29 need real names from vtable analysis.
Engine documentation - memory, rendering, scene systems
Initial docs/ written: memory-system.md (BestFitAllocator internals), rendering-pipeline.md (Dx9 fixed-function, billboard modes), scene-system.md (Scene lifecycle + Update state machine). Commit ab0ab42.