Repo hygiene: drop internal planning files, clean docs for publication

Untrack TODO.md, RELEASING.md, RELEASE_NOTES.md, ideas/ and
docs/CLAUDE_PROPER_OBJECT_REGISTRATION_PLAN.md -- internal planning and
agent notes with no reason to be published. Files stay on disk locally.

Replace remaining absolute machine paths in docs with relative ones
(one referenced a separate private project), and swap decorative emoji
for ASCII markers.

README: lead with the no-longer-actively-developed notice.
This commit is contained in:
MarcelineVQ
2026-08-01 18:03:46 -07:00
parent 4865b583d1
commit 69051fd486
14 changed files with 121 additions and 813 deletions
@@ -1,169 +0,0 @@
# Proper Object Registration Plan (for Claude)
## Goal
Stop ad-hoc list insertion and implement a **correct, engine-native object creation/registration path** for client-side markers in WoW 1.12.1.
Success = marker is visible in-world, no crash on create/destroy/toggle, and lifecycle uses native manager functions (not manual pointer surgery as primary path).
---
## Current Status Summary (Read First)
1. **Crash fixed, visibility not fixed**
- We eliminated crash caused by callback slot garbage (`obj+0x174`) in manager iteration path.
- Markers still invisible in both current test paths.
2. **What was tried**
- Path A: Allocate world object + attach model + manual insert into manager list (`0x00C7CAEC`).
- Path B: `CreateGameObject_WithProperties` + fallback manual insertion if unlinked.
- Both now create/destroy cleanly, still not rendering.
3. **Known engine behaviors**
- Manager iterator (`AllocateGameObject` region around `0x00683F80`) uses positions at `+0x5C/+0x60/+0x68` and calls render-management functions.
- It may call callback pointer at `+0x174` if non-zero.
- List pointers can be low-bit tagged.
4. **Conclusion so far**
- List manipulation and crash stability are mostly solved.
- Remaining blocker is likely **incorrect native registration/state initialization**, i.e. object not entering correct draw/classification path.
---
## Mandatory Context Files
Read these before coding:
1. `research/marker-visibility-debug-log.md`
2. `docs/M2_MODEL_SYSTEM.md`
3. `research/dynamic-object-research.md`
4. `src/markers/markers.zig`
5. Crash reference: `/media/bigfaststore/games/twmoa_1172/Errors/2026-02-27 07.11.41 Crash.txt`
Also review the CLI Ghidra skill:
- `~/.claude/skills/ghidra-cli-wow-re/SKILL.md`
---
## Rules of Engagement
1. **Do not treat manual list insertion as final architecture.**
2. **Do not trust decompiler signatures alone.** Verify calling conventions from bytes/prologue/RET cleanup.
3. **Prefer discovering native add/remove APIs** used by real visible objects.
4. **Every hypothesis must have an observable test log.**
5. **No broad refactors until registration path is proven.**
---
## Execution Plan
## Phase 1 - Identify canonical native registration path
### Task 1.1: Recover true iterator and linkage semantics
- Disassemble around `0x00683F80` (and nearby helpers) in detail.
- Map exactly how nodes are traversed and linked (including pointer tagging and indirection via globals like `0x00C7CAE4/0x00C7CAEC`).
- Produce pseudocode with verified offsets and conditions.
**Deliverable:** short technical note: "Manager traversal/link semantics".
### Task 1.2: Find native insertion/removal helpers
- Find xrefs to `0x00C7CAEC` and offsets `+0x16C/+0x170`.
- Identify functions that insert/remove objects in production code.
- Verify one candidate by callsite context (who calls it and when).
**Deliverable:** candidate helper function list with addresses + confidence.
### Task 1.3: Find real creator path for visible world objects
- Trace from known visible object creators (game objects/doodads/spell visuals) to registration.
- Determine **minimal required function chain** from creation to render visibility.
**Deliverable:** canonical call chain diagram with function addresses.
---
## Phase 2 - Build proper registration wrapper in markers module
### Task 2.1: Implement native-path wrapper
- Add a wrapper that uses discovered native creator/registration APIs.
- Manual list insertion may remain only as fallback/diagnostic mode, not default.
### Task 2.2: Lifecycle correctness
- Ensure destroy path uses matching native teardown/unlink APIs.
- Prevent double-unlink and callback garbage.
### Task 2.3: Instrumentation
- Keep concise debug logs for:
- object pointer
- model pointer
- registration result path
- destroy path confirmation
**Deliverable:** code in `src/markers/markers.zig` with clear mode labels.
---
## Phase 3 - Validation protocol (must pass)
Run and capture logs for:
1. `/mark on`
2. visually confirm marker appears
3. `/mark off`
4. repeat 5+ times
5. mode switching stress (`testa/testb` if retained)
Must pass:
- no crash
- no panic
- marker visible at player position
- clean destroy every cycle
If failed:
- report exact failing step, log lines, and next smallest probe
---
## Expected Outputs from Claude
1. Updated code implementing native registration path.
2. Updated `research/marker-visibility-debug-log.md` with:
- what changed
- what was validated
- remaining unknowns (if any)
3. Optional update to `docs/M2_MODEL_SYSTEM.md` for newly confirmed function semantics.
4. Final summary:
- root cause
- final fix
- evidence (logs + addresses)
---
## Suggested Command Workflow (CLI Ghidra)
Use headless scripts via:
```bash
<ghidra-scripts>/run-analysis.sh /tmp/analysis.py
```
Focus scripts on:
- xrefs to manager globals and list offsets
- disassembly around candidate insertion helpers
- call graph from visible object creation paths
---
## Non-Goals (for this pass)
- New marker visuals
- Effects polish
- Optimizations unrelated to visibility/registration correctness
- Expanding to many marker types before first visible stable marker
---
## Acceptance Criteria
- Marker visibility achieved using engine-native registration path (or proven minimal wrapper around it)
- No crash across repeated create/destroy
- Documentation updated with verified semantics
- Ad-hoc list surgery no longer required as primary behavior
+4 -4
View File
@@ -39,8 +39,8 @@ struct Model {
| Offset | Type | Name | Description | Verified |
|--------|------|------|-------------|----------|
| +0x30 | `void*` | dataPtr | Pointer to geometry data container | ✓ |
| +0x94 | `BoneMatrix*` | boneArray | **Pointer to bone matrix array** | ✓ |
| +0x30 | `void*` | dataPtr | Pointer to geometry data container | [OK] |
| +0x94 | `BoneMatrix*` | boneArray | **Pointer to bone matrix array** | [OK] |
### Nested Structure (Model+0x30)
@@ -148,8 +148,8 @@ struct MeshData {
| Offset | Type | Name | Description | Verified |
|--------|------|------|-------------|----------|
| +0x04 | `uint16_t` | vertexOffset | Start vertex in global array | ✓ |
| +0x06 | `uint16_t` | vertexCount | **Loop terminator in applyBoneTransforms** | ✓ |
| +0x04 | `uint16_t` | vertexOffset | Start vertex in global array | [OK] |
| +0x06 | `uint16_t` | vertexCount | **Loop terminator in applyBoneTransforms** | [OK] |
| +0x08 | `uint16_t` | indexOffset | Start index for this submesh | ? |
| +0x0A | `uint16_t` | indexCount | Number of indices | ? |
+15 -15
View File
@@ -1,15 +1,15 @@
# Hooking Quick Reference Guide
## 🚨 Critical Rules (Break These = Crash)
## Critical Rules (Break These = Crash)
### 1. NEVER Use PUSHA/POPA Around C Function Calls
```asm
; ❌ WRONG - Will crash
; [X] WRONG - Will crash
pusha
call _MyCFunction ; This modifies EAX/ECX/EDX
popa ; This RESTORES old values, breaking everything
; ✅ RIGHT - Only save non-volatile registers
; [OK] RIGHT - Only save non-volatile registers
pushl %ebx
pushl %esi
pushl %edi
@@ -26,18 +26,18 @@ popl %ebx
typedef void (__fastcall *Func_t)(void* thisPtr, void* edx, int arg);
static void __fastcall MyHook(void* thisPtr, void* edx, int arg) {
// thisPtr from ECX ✅
// edx is trash ✅
// arg from stack ✅
// thisPtr from ECX [OK]
// edx is trash [OK]
// arg from stack [OK]
}
```
### 3. Steal Complete Instructions Only
```cpp
// ❌ WRONG
// [X] WRONG
#define STOLEN_BYTES 6 // No idea if this splits an instruction
// ✅ RIGHT
// [OK] RIGHT
// Disassemble: PUSH EBP (1) + MOV EBP,ESP (2) + SUB ESP,0x80 (6) = 9 bytes
#define STOLEN_BYTES 9 // Documented complete instructions
```
@@ -54,7 +54,7 @@ static void __fastcall MyHook(void* thisPtr, void* edx, int arg) {
---
## 📋 Register Preservation Cheat Sheet
## Register Preservation Cheat Sheet
| Register | Type | Who Saves It? | Can Hook Modify? |
|----------|------|---------------|------------------|
@@ -69,7 +69,7 @@ static void __fastcall MyHook(void* thisPtr, void* edx, int arg) {
---
## 🎯 Calling Convention Quick Reference
## Calling Convention Quick Reference
### __stdcall (WINAPI)
- Args: stack (right to left)
@@ -93,7 +93,7 @@ static void __fastcall MyHook(void* thisPtr, void* edx, int arg) {
---
## 🔧 Safe Hook Templates
## Safe Hook Templates
### Template 1: VTable Hook (Safest)
```cpp
@@ -165,7 +165,7 @@ __attribute__((naked)) static void NakedHook() {
---
## 🛡️ Safety Checklist
## Safety Checklist
### Before Installing Hook:
- [ ] Disassemble target to verify prologue
@@ -190,7 +190,7 @@ __attribute__((naked)) static void NakedHook() {
---
## 🐛 Common Crash Causes
## Common Crash Causes
1. **PUSHA/POPA with C calls** → Volatile register corruption
2. **Wrong calling convention** → ECX/EDX clobbered when needed
@@ -203,7 +203,7 @@ __attribute__((naked)) static void NakedHook() {
---
## 📚 Research Sources
## Research Sources
- [How to Hook Functions - Guided Hacking](https://guidedhacking.com/threads/how-to-hook-functions-code-detouring-guide.14185/)
- [Thiscall Hooking - tresp4sser](https://tresp4sser.wordpress.com/2012/10/06/how-to-hook-thiscall-functions/)
@@ -213,7 +213,7 @@ __attribute__((naked)) static void NakedHook() {
---
## 💡 Key Insight
## Key Insight
**Hooking is NOT about blindly copying bytes.**
+21 -21
View File
@@ -5,62 +5,62 @@
## Critical Address Fixes
### ❌ ProcessWorldWithFrustum
### [X] ProcessWorldWithFrustum
- **Old Address:** `0x00683000` (WRONG - no function at this address)
- **Correct Address:** `0x00682fa0`
- **Ghidra Signature:** `undefined __fastcall ProcessWorldWithFrustum(float * frustumBounds)`
- **First Byte:** `0x57` (PUSH EDI)
- **Stolen Bytes:** 9 bytes
- **Status:** ✅ FIXED in discovery_hooks.cpp
- **Status:** [OK] FIXED in discovery_hooks.cpp
### ❌ RenderObjectsWithLOD
### [X] RenderObjectsWithLOD
- **Old Address:** `0x00684600` (WRONG - no function at this address)
- **Correct Address:** `0x00684510`
- **Ghidra Signature:** `undefined __stdcall RenderObjectsWithLOD(void)`
- **First Byte:** `0x55` (PUSH EBP)
- **Stolen Bytes:** 8 bytes
- **Status:** ✅ FIXED in discovery_hooks.cpp
- **Status:** [OK] FIXED in discovery_hooks.cpp
## Verified Correct Addresses
### ✅ ScenePresent
### [OK] ScenePresent
- **Address:** `0x0059a870`
- **Ghidra Signature:** `void __fastcall CGxDeviceD3d::ScenePresent(CGxDeviceD3d * device)`
- **First Byte:** `0x55` (PUSH EBP)
- **Stolen Bytes:** 6 bytes
- **Calling Convention:** __fastcall (saves ECX+EDX) ✅ Correct in code
- **Calling Convention:** __fastcall (saves ECX+EDX) [OK] Correct in code
### ✅ CullAndProcessWorldChunks
### [OK] CullAndProcessWorldChunks
- **Address:** `0x00683040`
- **Ghidra Signature:** `undefined __stdcall CullAndProcessWorldChunks(void)`
- **First Byte:** `0x55` (PUSH EBP)
- **Stolen Bytes:** 8 bytes
- **Calling Convention:** __stdcall (no register saves) ✅ Correct in code
- **Calling Convention:** __stdcall (no register saves) [OK] Correct in code
### ✅ ProcessStaticObjectsCulling
### [OK] ProcessStaticObjectsCulling
- **Address:** `0x00683bf0`
- **Ghidra Signature:** `undefined __fastcall ProcessStaticObjectsCulling(int staticObjectManager)`
- **First Byte:** `0x55` (PUSH EBP)
- **Stolen Bytes:** 9 bytes
- **Calling Convention:** __fastcall (saves ECX+EDX) ✅ Correct in code
- **Calling Convention:** __fastcall (saves ECX+EDX) [OK] Correct in code
### ✅ CM2Scene_DrawModelBatch
### [OK] CM2Scene_DrawModelBatch
- **Address:** `0x0070cf70`
- **Ghidra Signature:** `undefined __fastcall CM2Scene_DrawModelBatch(void * renderContext)`
- **First Byte:** `0x55` (PUSH EBP)
- **Stolen Bytes:** 8 bytes
- **Calling Convention:** __fastcall (saves ECX+EDX) ✅ Correct in code
- **Calling Convention:** __fastcall (saves ECX+EDX) [OK] Correct in code
## Calling Convention Summary
| Function | Ghidra Convention | Code Implementation | Status |
|----------|------------------|---------------------|--------|
| ScenePresent | __fastcall | __fastcall (ECX+EDX) | ✅ Correct |
| ProcessWorldWithFrustum | __fastcall | __fastcall (ECX+EDX) | ✅ Correct |
| CullAndProcessWorldChunks | __stdcall | __cdecl (no saves) | ✅ OK (both no reg saves) |
| ProcessStaticObjectsCulling | __fastcall | __fastcall (ECX+EDX) | ✅ Correct |
| RenderObjectsWithLOD | __stdcall | __cdecl (no saves) | ✅ OK (both no reg saves) |
| CM2Scene_DrawModelBatch | __fastcall | __fastcall (ECX+EDX) | ✅ Correct |
| ScenePresent | __fastcall | __fastcall (ECX+EDX) | [OK] Correct |
| ProcessWorldWithFrustum | __fastcall | __fastcall (ECX+EDX) | [OK] Correct |
| CullAndProcessWorldChunks | __stdcall | __cdecl (no saves) | OK (both no reg saves) |
| ProcessStaticObjectsCulling | __fastcall | __fastcall (ECX+EDX) | [OK] Correct |
| RenderObjectsWithLOD | __stdcall | __cdecl (no saves) | OK (both no reg saves) |
| CM2Scene_DrawModelBatch | __fastcall | __fastcall (ECX+EDX) | [OK] Correct |
## Prologue Byte Analysis
@@ -87,9 +87,9 @@ The Ghidra MCP `get_bytes` tool only returns 1 byte regardless of the length par
## Recommendations
1. ✅ **Address fixes have been applied** to discovery_hooks.cpp
2. ⚠️ **Test the DLL** after recompilation to verify hooks work correctly
3. ⚠️ **Monitor for crashes** that could indicate incorrect stolen byte counts
1. [OK] **Address fixes have been applied** to discovery_hooks.cpp
2. [!] **Test the DLL** after recompilation to verify hooks work correctly
3. [!] **Monitor for crashes** that could indicate incorrect stolen byte counts
4. If crashes occur after address fix, use Ghidra GUI to manually count instruction bytes in prologues
## Next Steps
+9 -9
View File
@@ -14,9 +14,9 @@ Any architectural approach is acceptable. The existing 3-pass stencil code is pr
The depth buffer doesn't distinguish "wall pixel" from "player pixel." After a frame is rendered, there's no way to know which depth values came from static geometry vs units. This makes pure post-process approaches (mask + edge detection in EndScene) unable to satisfy requirements 1 and 2 simultaneously:
- If you depth-test the mask render against the full depth buffer → walls occlude (✓) but players also occlude (✗)
- If you skip depth testing → players don't occlude (✓) but walls also don't occlude (✗)
- If you composite in EndScene after all rendering → outlines are on top of everything, including walls (✗)
- If you depth-test the mask render against the full depth buffer → walls occlude ([OK]) but players also occlude ([X])
- If you skip depth testing → players don't occlude ([OK]) but walls also don't occlude ([X])
- If you composite in EndScene after all rendering → outlines are on top of everything, including walls ([X])
**You need depth information from BEFORE players/NPCs have rendered, but AFTER terrain/WMOs have rendered.** This is only available at a specific point during the frame - when M2 model batches begin processing.
@@ -26,9 +26,9 @@ The depth buffer doesn't distinguish "wall pixel" from "player pixel." After a f
**How it satisfies the requirements:**
1. **Batch reordering** moves outline targets to render first in the M2 batch list. At that point, only terrain + WMO depth exists in the depth buffer. The outline pass (pass 2) uses `ZENABLE=TRUE` → terrain/WMOs occlude the outline. ✓
2. **Stencil bit** (`STENCIL_BIT_OUTLINE=0x02`) is written where outline pixels are drawn. Subsequent player/NPC batches test against this bit and fail where outline exists → players can't paint over outlines. ✓
3. **Dead players** use `ZENABLE=FALSE` in pass 2 → outline ignores all depth → visible through walls. ✓
1. **Batch reordering** moves outline targets to render first in the M2 batch list. At that point, only terrain + WMO depth exists in the depth buffer. The outline pass (pass 2) uses `ZENABLE=TRUE` → terrain/WMOs occlude the outline. [OK]
2. **Stencil bit** (`STENCIL_BIT_OUTLINE=0x02`) is written where outline pixels are drawn. Subsequent player/NPC batches test against this bit and fail where outline exists → players can't paint over outlines. [OK]
3. **Dead players** use `ZENABLE=FALSE` in pass 2 → outline ignores all depth → visible through walls. [OK]
**Downsides:**
- 3 DIP calls per outline target (stencil mark + outline + normal redraw)
@@ -79,9 +79,9 @@ This is what the current code already does, just cleaned up.
Given the requirements, the 3-pass stencil with batch reordering is the only approach that works without exotic depth buffer tricks. The key properties it provides:
1. **Outline renders when only static-world depth exists** (via batch reordering) → walls/terrain occlude ✓
2. **Stencil bit prevents later units from overwriting outline** → players don't occlude ✓
3. **Per-category depth disable** → dead player outlines through walls ✓
1. **Outline renders when only static-world depth exists** (via batch reordering) → walls/terrain occlude [OK]
2. **Stencil bit prevents later units from overwriting outline** → players don't occlude [OK]
3. **Per-category depth disable** → dead player outlines through walls [OK]
## Improvements to Implement
+53 -55
View File
@@ -49,7 +49,7 @@ Functions in rendering vtable:
### World Rendering Orchestration (FOUND!)
#### **ProcessWorldWithFrustum** ⭐ MAIN WORLD ORCHESTRATOR
#### **ProcessWorldWithFrustum** MAIN WORLD ORCHESTRATOR
- **Address**: `0x00683000` (called at 0x0068302f)
- **Signature**: `void __fastcall ProcessWorldWithFrustum(float *frustumBounds)`
- **Role**: **PRIMARY WORLD RENDERING ORCHESTRATOR** - Coordinates terrain, objects, and models
@@ -65,8 +65,7 @@ Functions in rendering vtable:
- **`CullObjectsToRenderList(manager, 2)`** - Cull objects type 2
- `ProcessStaticObjectsCulling()` - Cull static world objects
4. `PopFrustumStack()` - Restore frustum state
5. **`CullAndProcessWorldChunks()`** - Terrain chunk rendering ⭐
- **Notes**: This is the function that needs to be hooked for complete render order control!
5. **`CullAndProcessWorldChunks()`** - Terrain chunk rendering - **Notes**: This is the function that needs to be hooked for complete render order control!
#### **CullObjectsToRenderList**
- **Address**: `0x00683ab0`
@@ -161,7 +160,7 @@ Functions in rendering vtable:
### Terrain/Water Rendering
#### **CullAndProcessWorldChunks** ⭐ TERRAIN RENDERER
#### **CullAndProcessWorldChunks** TERRAIN RENDERER
- **Address**: `0x00683040`
- **Called by**: `ProcessWorldWithFrustum` (after object culling)
- **Signature**: `void CullAndProcessWorldChunks(void)`
@@ -219,7 +218,7 @@ Functions in rendering vtable:
#### **renderMeshWithLOD**
- **Called by**: `RenderObjectsWithLOD` (0x00684510)
- **Role**: **ACTUAL MESH DRAWING FUNCTION** - Likely calls DrawIndexedPrimitive
- **Status**: ⚠️ Function body not yet located - CRITICAL to find!
- **Status**: [!] Function body not yet located - CRITICAL to find!
- **Importance**: This is where the final D3D drawing happens
#### **Function Pointer Rendering (CM2Scene_DrawModelBatch)**
@@ -283,7 +282,7 @@ WoW uses multiple linked lists to manage culled objects that need to be rendered
- **Data Address**: `DAT_00c7cb18` + offset (indexed by renderListIndex * 0xc)
- **Indices**: 0, 1, 2 (likely: terrain objects, WMOs, M2 models)
- **Populated by**: `CullObjectsToRenderList` (0x00683ab0)
- **Processed by**: Unknown (⚠️ still need to find)
- **Processed by**: Unknown ([!] still need to find)
#### **Static Object Render Lists (ProcessStaticObjectsCulling)**
- **List 1 (Normal)**:
@@ -303,7 +302,7 @@ WoW uses multiple linked lists to manage culled objects that need to be rendered
#### **LOD Object Render List (RenderObjectsWithLOD)**
- **List Head**: `PTR_00c7cae0`
- **Offset**: `PTR_00c7cad8`
- **Populated by**: Unknown (⚠️ likely populated during culling phase)
- **Populated by**: Unknown ([!] likely populated during culling phase)
- **Processed by**: `RenderObjectsWithLOD` (0x00684510)
- **Notes**: Objects are rendered with LOD (Level of Detail) based on distance
@@ -336,32 +335,32 @@ WoW uses multiple linked lists to manage culled objects that need to be rendered
- **Setup**: Per-stage configuration via `SetTextureStage()`
- **Transforms**: Applied to stages 0 and 1 during model batch rendering
## COMPLETE RENDERING PIPELINE DISCOVERED! ✅
## COMPLETE RENDERING PIPELINE DISCOVERED! [OK]
### What We Found
#### ✅ **Main World Orchestrator**: `ProcessWorldWithFrustum` (0x00683000)
#### [OK] **Main World Orchestrator**: `ProcessWorldWithFrustum` (0x00683000)
- Coordinates all world rendering
- Culls objects to 3 render lists (indices 0, 1, 2)
- Renders terrain chunks last via `CullAndProcessWorldChunks`
#### ✅ **Terrain Renderer**: `CullAndProcessWorldChunks` (0x00683040)
#### [OK] **Terrain Renderer**: `CullAndProcessWorldChunks` (0x00683040)
- Primary terrain chunk culling and rendering
- Processes ADT tiles within camera frustum
- **Currently renders AFTER objects** (called last in ProcessWorldWithFrustum)
#### ✅ **Object Culling**: `CullObjectsToRenderList` (0x00683ab0)
#### [OK] **Object Culling**: `CullObjectsToRenderList` (0x00683ab0)
- Called 3 times with indices: 1, 0, 2
- Likely represents: terrain objects, WMOs, and M2 models
- Frustum culls and adds to render lists
#### ✅ **Static Object Culling**: `ProcessStaticObjectsCulling` (0x00683bf0)
#### [OK] **Static Object Culling**: `ProcessStaticObjectsCulling` (0x00683bf0)
- Handles WMO groups and static doodads
- Respects render flags
### ✅ **NEW DISCOVERIES** - Render List Processing Found!
### [OK] **NEW DISCOVERIES** - Render List Processing Found!
#### ⭐ **RenderObjectsWithLOD** - RENDER LIST PROCESSOR
#### **RenderObjectsWithLOD** - RENDER LIST PROCESSOR
- **Address**: `0x00684510`
- **Role**: **PRIMARY RENDER LIST PROCESSOR** - Iterates through object render list and draws them
- **Signature**: `void RenderObjectsWithLOD(void)`
@@ -376,14 +375,13 @@ WoW uses multiple linked lists to manage culled objects that need to be rendered
- Sets render target
- Calls `ProcessObjectGeometry()` to prepare geometry
- Calls `UpdateObjectPosition()` to update transform
- **Calls `renderMeshWithLOD()` to actually draw the mesh** ⭐
5. Checks LOD distance (`_DAT_00867958`) to cull distant objects
- **Calls `renderMeshWithLOD()` to actually draw the mesh** 5. Checks LOD distance (`_DAT_00867958`) to cull distant objects
6. Calls `EndRender()` to finish rendering
- **Notes**: This is the missing link between culling and rendering!
- **Calls**: `renderMeshWithLOD()`, `ProcessObjectGeometry()`, `UpdateObjectPosition()`
- **Status**: No xrefs found - likely called via function pointer or vtable
#### ⭐ **executeRenderCommands** - WMO/STATIC OBJECT RENDERER
#### **executeRenderCommands** - WMO/STATIC OBJECT RENDERER
- **Called by**: `ProcessStaticObjectsCulling` (at end of function)
- **Role**: Renders static objects (WMOs, buildings, doodads) from the culled list
- **Implementation**: Found inside `ProcessStaticObjectsCulling` as a loop:
@@ -399,24 +397,24 @@ WoW uses multiple linked lists to manage culled objects that need to be rendered
### Still To Investigate
#### 🟡 **Main Frame/Game Loop**
#### [~] **Main Frame/Game Loop**
- **Status**: Need to find what calls `ProcessWorldWithFrustum`
- **Question**: What is the top-level orchestrator above ProcessWorldWithFrustum?
- **Hypothesis**: Called from main game loop, possibly via callback or vtable
- **Search Strategy**: Look for functions that reference ScenePresent vtable or main loop
#### 🟡 **RenderObjectsWithLOD Caller**
#### [~] **RenderObjectsWithLOD Caller**
- **Status**: No xrefs found to this function
- **Question**: Where/how is `RenderObjectsWithLOD` called?
- **Hypothesis**: Called via function pointer after ProcessWorldWithFrustum completes
- **Likely**: Part of render list processing phase between culling and present
#### 🟡 **renderMeshWithLOD Implementation**
#### [~] **renderMeshWithLOD Implementation**
- **Status**: Called by RenderObjectsWithLOD, but function not yet located
- **Role**: Actual mesh drawing function - likely calls DrawIndexedPrimitive
- **Need**: Find this function to understand final rendering stage
#### 🟡 **Connection to M2Scene**
#### [~] **Connection to M2Scene**
- **Status**: M2 model rendering chain is clear, but connection to world rendering unclear
- **Question**: How does `CM2Scene_ExecuteRenderPass` get called in relation to `ProcessWorldWithFrustum`?
- **Need**: Find the higher-level function that calls both
@@ -544,41 +542,41 @@ Once terrain/WMO renderers are found:
## Summary
### ✅ MAJOR PROGRESS - Rendering Pipeline Nearly Complete!
### [OK] MAJOR PROGRESS - Rendering Pipeline Nearly Complete!
This analysis has successfully discovered most of the WoW rendering architecture:
#### **Primary Discoveries**
1. **✅ World Orchestrator**: `ProcessWorldWithFrustum` (0x00683000)
1. **[OK] World Orchestrator**: `ProcessWorldWithFrustum` (0x00683000)
- Coordinates frustum culling for all world objects
- Processes 32 object managers with 3 render list types
- Calls terrain renderer last
2. **✅ Terrain Renderer**: `CullAndProcessWorldChunks` (0x00683040)
2. **[OK] Terrain Renderer**: `CullAndProcessWorldChunks` (0x00683040)
- Primary ADT terrain chunk culling and rendering
- Called AFTER object culling (currently renders terrain depth last)
3. **✅ Object Culling**: `CullObjectsToRenderList` (0x00683ab0)
3. **[OK] Object Culling**: `CullObjectsToRenderList` (0x00683ab0)
- Frustum culls objects to 3 render lists (indices: 1, 0, 2)
- Adds to global render lists at PTR_00c7cb14/PTR_00c7cb18
- Likely: terrain objects, WMOs, M2 models
4. **✅ Static Culling**: `ProcessStaticObjectsCulling` (0x00683bf0)
4. **[OK] Static Culling**: `ProcessStaticObjectsCulling` (0x00683bf0)
- Handles WMO groups and static doodads
- **Contains inline WMO renderer** (`executeRenderCommands` loop at end)
- Adds to render lists at PTR_00c7cadc/PTR_00c7cb54
5. **⭐ NEW - Render List Processor**: `RenderObjectsWithLOD` (0x00684510)
5. **NEW - Render List Processor**: `RenderObjectsWithLOD` (0x00684510)
- **CRITICAL DISCOVERY**: This processes the culled object render list!
- Iterates through objects at PTR_00c7cae0
- Calls `renderMeshWithLOD()` for actual drawing
- Includes LOD distance culling
6. **✅ M2 Model Pipeline**: Complete chain from `CM2Scene_ExecuteRenderPass` → `CM2SceneRenderDraw` → `RenderBatches` → `CM2Scene_DrawModelBatch`
6. **[OK] M2 Model Pipeline**: Complete chain from `CM2Scene_ExecuteRenderPass` → `CM2SceneRenderDraw` → `RenderBatches` → `CM2Scene_DrawModelBatch`
7. **✅ Render Lists Mapped**: Documented 7+ global render list structures and their usage
7. **[OK] Render Lists Mapped**: Documented 7+ global render list structures and their usage
### 🎯 Updated Rendering Flow
### Updated Rendering Flow
Based on discoveries, the likely flow is:
@@ -597,7 +595,7 @@ Based on discoveries, the likely flow is:
- Cursor overlay
- ISceneEnd → D3D Present
### 🎯 Key Insight for Selective Occlusion
### Key Insight for Selective Occlusion
The render list processing happens AFTER culling. To control occlusion:
@@ -613,10 +611,10 @@ The render list processing happens AFTER culling. To control occlusion:
4. Normal objects/players
### Remaining Critical Questions
- ⚠️ **What calls RenderObjectsWithLOD?** (no xrefs found - likely function pointer or callback system)
- ✅ **Found rendering mechanism**: Function pointer at `renderContext+0x40+0x11c` in CM2Scene_DrawModelBatch performs actual D3D draw
- ⚠️ **Main game loop?** (what calls ProcessWorldWithFrustum? - no direct callers found, likely callback/vtable)
- ⚠️ **Render order?** (do objects render before or after terrain?)
- [!] **What calls RenderObjectsWithLOD?** (no xrefs found - likely function pointer or callback system)
- [OK] **Found rendering mechanism**: Function pointer at `renderContext+0x40+0x11c` in CM2Scene_DrawModelBatch performs actual D3D draw
- [!] **Main game loop?** (what calls ProcessWorldWithFrustum? - no direct callers found, likely callback/vtable)
- [!] **Render order?** (do objects render before or after terrain?)
### Latest Discoveries (Session 2)
@@ -639,7 +637,7 @@ We now have MOST of the critical hook points for selective occlusion! The missin
---
## 🎯 COMPLETE HOOK POINT MAPPING (Session 3)
## COMPLETE HOOK POINT MAPPING (Session 3)
### Terrain Rendering Pipeline
**Culling**: `CullAndProcessWorldChunks` (0x00683040)
@@ -702,7 +700,7 @@ We now have MOST of the critical hook points for selective occlusion! The missin
---
## 📋 RECOMMENDED HOOK STRATEGY FOR SELECTIVE OCCLUSION
## RECOMMENDED HOOK STRATEGY FOR SELECTIVE OCCLUSION
### Option A: High-Level (Easiest)
Hook `ProcessWorldWithFrustum` (0x00683000) to reorder rendering:
@@ -730,27 +728,27 @@ Hook D3D device:
---
## ✅ FINAL DISCOVERY STATUS
## [OK] FINAL DISCOVERY STATUS
### Completed ✅
- ✅ Terrain culling function (CullAndProcessWorldChunks)
- ✅ WMO culling AND rendering (ProcessStaticObjectsCulling with inline rendering)
- ✅ Object culling (CullObjectsToRenderList)
- ✅ Object processing (RenderObjectsWithLOD)
- ✅ Function pointer rendering system (CM2Scene_DrawModelBatch)
- ✅ Main world orchestrator (ProcessWorldWithFrustum)
- ✅ Callback registration system (UpdateRenderCallback)
- ✅ All render list structures mapped (7+ globals)
### Completed [OK]
- [OK] Terrain culling function (CullAndProcessWorldChunks)
- [OK] WMO culling AND rendering (ProcessStaticObjectsCulling with inline rendering)
- [OK] Object culling (CullObjectsToRenderList)
- [OK] Object processing (RenderObjectsWithLOD)
- [OK] Function pointer rendering system (CM2Scene_DrawModelBatch)
- [OK] Main world orchestrator (ProcessWorldWithFrustum)
- [OK] Callback registration system (UpdateRenderCallback)
- [OK] All render list structures mapped (7+ globals)
### Architecture Understanding ✅
- ✅ WoW uses callback-based rendering (no direct function calls found)
- ✅ Rendering is event-driven through vtables and function pointers
- ✅ Terrain rendering likely invoked through callback (not found statically)
- ✅ Static analysis limited by callback architecture
### Architecture Understanding [OK]
- [OK] WoW uses callback-based rendering (no direct function calls found)
- [OK] Rendering is event-driven through vtables and function pointers
- [OK] Terrain rendering likely invoked through callback (not found statically)
- [OK] Static analysis limited by callback architecture
### Remaining Unknowns (Acceptable)
- ⚠️ Exact terrain render list processor (likely callback - can find at runtime)
- ⚠️ Main game loop that invokes ProcessWorldWithFrustum (callback system)
- ⚠️ Exact render order (can determine through runtime hooking)
- [!] Exact terrain render list processor (likely callback - can find at runtime)
- [!] Main game loop that invokes ProcessWorldWithFrustum (callback system)
- [!] Exact render order (can determine through runtime hooking)
**Conclusion**: We have ALL the hook points needed for selective occlusion implementation. Runtime debugging can fill in the remaining details if needed.
+14 -14
View File
@@ -120,11 +120,11 @@ pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
### Implementation Status [COMPLETED]
All objectives achieved - see "Working Implementation" at end of document:
- ✅ Hook DrawIndexedPrimitive to detect corpse/target/raid-marked models
- ✅ Stencil-based outline rendering in EndScene
- ✅ Custom vertex shader for bone-animated outline expansion
- ✅ Per-category visual effects (dark halo for dead, colored outline for marks/target)
- ✅ Through-wall visibility with proper body/outline layering
- [OK] Hook DrawIndexedPrimitive to detect corpse/target/raid-marked models
- [OK] Stencil-based outline rendering in EndScene
- [OK] Custom vertex shader for bone-animated outline expansion
- [OK] Per-category visual effects (dark halo for dead, colored outline for marks/target)
- [OK] Through-wall visibility with proper body/outline layering
### D3D9 "Chams" / Wallhack Technique
@@ -1780,9 +1780,9 @@ c7: 0.000 1.000 0.000 0.000 <- Y identity
**Attempted:** Scale c0.x, c1.y, c2.x, c3.y by `g_outlineThickness` (1.2x)
**Result:**
- Model silhouette appears larger ✓
- But silhouette MOVES when view angle changes ✗
- Silhouette covers corpse instead of outlining it ✗
- Model silhouette appears larger [OK]
- But silhouette MOVES when view angle changes [X]
- Silhouette covers corpse instead of outlining it [X]
**Root Cause:** Scaling these projection-space constants distorts the view transformation, not the model. The model center isn't at origin in view space, so scaling pushes it in different directions based on camera angle.
@@ -2260,9 +2260,9 @@ Self-outline prevention: Local player is excluded from target and raid mark outl
### Status
- ✅ Full bone transform shader working
- ✅ Stencil-based outline rendering
- ✅ Per-category visual effects (outline vs dark halo)
- ✅ Distance-based thickness scaling with min/max clamping
- ✅ Through-wall visibility
- ✅ Self-outline prevention
- [OK] Full bone transform shader working
- [OK] Stencil-based outline rendering
- [OK] Per-category visual effects (outline vs dark halo)
- [OK] Distance-based thickness scaling with min/max clamping
- [OK] Through-wall visibility
- [OK] Self-outline prevention
+2 -2
View File
@@ -47,7 +47,7 @@ Consider alternative approaches:
- **Screen-space dilation** (iterative morphological expand of silhouette) - simpler, no distance field needed
- **Gaussian blur difference** - blur silhouette, subtract original, threshold
- **Sobel/edge detection** on the silhouette RT
- Docs in `/media/storage/projects/zig/weirdutils/docs/` describe these alternatives
- Docs in `docs/` describe these alternatives
- Reference articles: ameye.dev "5 ways to draw an outline", Ben Golus "Quest for Very Wide Outlines"
## Key Files
@@ -58,7 +58,7 @@ Consider alternative approaches:
- `reference/c_overlay/d3d9_hook.cpp` - C reference (uses 3-pass shell extrusion, not JFA)
## Build / Environment
- `zig build` from `/media/storage/projects/zig/weirdutils/`
- `zig build` from the repo root
- Zig 0.15, target x86-windows-msvc (32-bit DLL injected into WoW 3.3.5)
- Linux host (Arch, kernel 6.16.1), game via Wine/DXVK
- Git last commit: `661e156` (game object occlusion fix)
+1 -1
View File
@@ -1,6 +1,6 @@
# Outline Port Notes
Pure Zig reimplementation of the WoW 1.12.1 unit outline system, ported from the Idris C/C++ reference at `/media/storage/projects/idris/dlls`.
Pure Zig reimplementation of the WoW 1.12.1 unit outline system, ported from the earlier Idris C/C++ reference implementation (`reference/c_overlay/`, not included in this repo).
## Hooked Functions