# TLC parity ledger

Facts below are either asserted by tests against a live Steam install of
*Fable: The Lost Chapters*, or recorded here as a failed decode so we do not
retry the same wrong guess. Game assets stay in Steam. This repo only ships
parsers and notes.

## Evidence gate

Parity behavior must not be invented to bridge an unexplained gap. Before a
runtime, lifecycle, UI, audio, or rendering behavior is implemented, record the
retail call site, data field, asset value, or observed retail trace which proves
it. If the evidence is incomplete, leave the behavior explicitly `UNREAD` or
`PARTIAL`; do not add a plausible queue, timer, state transition, fallback, or
constant. Tests may lock recovered behavior, but passing tests are not evidence
that an unrecovered retail behavior is correct.

## Worked

| Piece | What we know | Locked by |
|---|---|---|
| Retail pump after PlayAVI | `0042EC7C` writes slot RGBA to `[0x13961E0]` before `006286F0`. After the third video: `[0x13B8616]==0` skips `009A8840`, `0042E98F` binds UI (`00595582` → `+180`, `005958F5`, **`00598A1C(0)`** `0041DB1D` `UI_FRONTEND_PRESS_START_MENU` slot `0x14` then msg `0xE5` vtbl+284). `0059899A` / `MAIN_MENU_*` is later, not first-seen. Then `0042E204` Init Engine, `0042DB40` 16-byte helper, **`009D8CF0`+`009BEEB0`**, then `0042DED5(0)` / `005952C3` / `0062F800` / `0062F8B0` / `0040F0E0`. Dest is `00628B79` width-fit. Invented contain/pillarbox is DISPROVEN. | `EngineLifecycleTests.Retail_0042EC7C_after_AVI_clears_then_inits_frontend`, `WorldSceneTests.PlayAVI_00628B79_resizes_to_viewport_width_and_centers` |
| Frontend dest / DIP | `UI_FRONTEND_PRESS_START_MENU` persist `Type=10`. Ctor `0054E3D0`. Children FOREST/TITLE/TEXT/LIST/LEGAL/MOUSE. Draw `00530260`. Sprites from `frontend.big` `GBANK_FRONT_END_PC`. Type-6 text `0054F5C0` / draw `0054EF00`. MAIN face ctor `00AB8E10` / glyph `00AB96B0` (22 file bytes, per-character width in the record). Atlas A8 at `file-w*h`. UV→pixel origin still PARTIAL. Dumps under `export/` (gitignored). | `EngineLifecycleTests.Frontend_PRESS_START_is_type_10_with_text_child`, `FontFileTests.ENG_ARIAL_16_atlas_is_512x256_packed_arial` |
| Leave Press Start | `0059A238` msg **`0xE5`** → `00599D5C`. First-seen `005955AB` empty → `00595845` (`[ui+160]=1`). Same-frame `00599E3F` → `00596917` slot `0x17` `UI_FRONTEND_NEW_PROFILE_SCREEN`. `00851770` binds `UI_NEW_PROFILE_EDIT_BOX` type 37 and seeds `004069E0`: game singleton 0 falls through to UTF-16 `0x122DE80` **"Default"**. Msg **`0x126`** (`UI_ACCEPT_NEW_PROFILE` def) → `00851920` `[+5]=1`. Next `00599E3F` → `0059697A` / `004067C0` `00999AB0` writable → `00595A06` `MAIN_MENU_NO_LIVEAWARE_NO_CONTINUE`. Then msg 15 → Leave. Native key that posts `0xE5`/`0x126` is UNREAD (`0041E6D3` is the consumer). Host Return→msg 15 from Press Start is DISPROVEN. | `EngineLifecycleTests.Frontend_00851770_seeds_Default_then_0x126_is_0059697A_main_menu` |
| New Game click | `0059A238` is UI vtbl+32 (`012521C8`). Message **15** → `0059A2DA` `[ui+28].vtbl+16` then `00594F28` `[retail+41]=1`. Pump reads +41 → Leave `0042F2A2`. Not `00DBDE40`. Keyboard N/Enter is only a host queue into that message **after** Press Start. | `EngineLifecycleTests.Frontend_0059A238_message_15_sets_retail_41` |
| Leave frontend / Init Game | `0042F2A2`: `[0x1375448]=0`, skip bank swaps when `[0x13B8616]==0`, `00404490`/`004131A0`, `FinalAlbion.wld`, `0042EBB6` (`+41` skips audio stop, `009BE420`+`009BEEB0`). Then `00418DCA` + vtbl+4 `004184BD`: `[0x13B86A0]=game`, named stages, `[0x13B8648]==0` Load Particles, vtbl+32 `00416953`, `[+90592]=1`. | `EngineLifecycleTests.New_game_is_leave_frontend_then_FinalAlbion_wld` |
| Loading world `00416953` | No-save: `[world].vtbl+28([game+40])`, `[+90588]` empty skips `004A3200`. Path is `+90576` `FinalAlbion.wld` from Leave `0042F44D` / ctor `00415E17` — not the `0x122EE14` UTF-16 `updatedscenic.wld` fallback. Then `004A1840` (WLD / QST `004A0D90` / Startup WAD / Set Static Map). `[0x13B8648]==0` → `0049F180` Init Characters/GUI/Quests, Activate Initial Quests (`+90584` empty vs `0x122D70E` → `004B4A10`). Always `004BBC00` (`ret 4`). Editor `[0x13B8648]!=0` and Loading save are UNREAD. Not a region load. | `EngineLifecycleTests.LoadWorld_00416953_no_save_is_004A1840_then_0049F180` |
| `004A1840` after Startup WAD | QST first (`0049D770` `.qst` + `GlobalQuests.qst`), `004FDAB0` empty, Startup WAD, `[0x1375456]==0` skip, world `vtbl+8` `0049E220` → `00507C30`, `+128=1`, `006C20A0` empty (no `006C27A0` from WLD parse), `[0x1375446]==0` skip Generate Offline Data, then Set Static Map. Host WLD-before-WAD is DISPROVEN. | `EngineLifecycleTests.LoadWorld_004A1840_after_wad_is_00507C30_then_empty_006C20A0` |
| Init Game suffix after `00416953` | No-save `[0x13B8648]==0`: `0049BA70` / `00416392` / `004AE9D0`, then `default_user.ini` miss and `user.ini` `009EC890` → `009EC710` → `009EB430`. First TLC line `SetMaxAnisotropy` has no `.text` name → `009EB260` unknown. `RunScript("joystick.ini")` is `009ECB70` → `009EC890` / `00999230` miss. `ActivateQuest("Gameflow")` is `00419CE0` → `[world+56]` Init Scripts `006E7740` vtbl `01260F0C+1104` **`00892E80`** → `004B4A10(1,1)` → `004B4260` → `00CB5AD0`. Direct `00CB5AD0` from user.ini is DISPROVEN. `[0x1375454]=1` `.data` so `004B3CE0` constructs (BSS-0 stub DISPROVEN). Gameflow flag 0 → `004AFA10` reuses Sunnyvale `00CDBD20`. `00CB7900` → `00CE6CF0` (`008A9DB0`/`008AE660` insert `OV_INTRO`…`SNOWSPIRE_ARRIVAL` at `0x13BAE44`) then `00CE75B0` `Main` watcher `00CDD450`/`00CB7E50`. `S_GF` `CCutsceneDef` at activate is DISPROVEN. Seed `004167DA` / `+90592`. `00501450` not reached (0 E8 / 0 imm). | `EngineLifecycleTests.Gameflow_00CE75B0_is_Main_watcher_not_S_GF` |
| First `004189C2` pump tail | After dummy `004FC180` index 0: `0040D2A0` alloc `0x140` ctor `0040CEC0` (`+51=1`) so `0040BC80` → `00407370` + `0040A7F0` (body PARTIAL). Then `[game+40]+44` vtbl+220 `00B239A0` writes `+24=1`, `+28=12`, `+32=20.0f` (`0x122F160`). `009F2660`/`009F26B0` on `[0x13CAA90]`. Inner `0098E1B0` ret; `009A6460` first-seen 1; `[game+52]==0` → `009F8BA0(game+90556)` then `004162B5`. Does not `E8` `00501450`. | `EngineLifecycleTests.First_pump_004189C2_is_0040D2A0_then_00B239A0_not_a_region` |
| After first `004162B5` | `00416202` → `0049B9E0` on `game+90488` (`0049BA70` ring, cap 60, float stride 4, `0049B9A0` mean at +40). `00415E85` `[0x13B85F1]==0` (no `.text` writer) skips the memlog body. `0044C6B0` then `009AC9E0 ret 4`. Host noted `00415E85` before `004162B5` as if the log ran — DISPROVEN. | `EngineLifecycleTests.First_pump_00416202_is_0049B9E0_then_00415E85_skip` |
| After `009AC9E0` | `cmp [game+8]`; first-seen 0 (`009A6460` `[engine+8]==0` → 1) so `je 00418AB1` loops. Second host `Pump` is that inner iteration, not `00501450`. Host `EnqueueAfterDummy` on second `Pump` is DISPROVEN. | `EngineLifecycleTests.Second_pump_004189C2_loops_inner_not_00501450` |
| `004189C2` leave | `009A6460` always `009A6370` (`009A4F20` PeekMessage first-seen empty, `009C00C0` TestCooperativeLevel). `[engine+8]` is written only by WndProc `009A5B60` `WM_DESTROY` (`0x9A5F7C[1]=009A5BEA`). That returns 2, `[game+8]=1`, `004175E5`. First-seen New Game does not post `WM_DESTROY`. Not `00501450`. | `EngineLifecycleTests.WmDestroy_009A5BEA_sets_engine_plus8_and_leaves_004189C2` |
| After empty PeekMessage | `00403079` `[opt+20]=5` (`[0x1375449]` no writer). Bit 0x01 → `00A60050`/`009A7180` `[engine+88]`. Then `009F4E20(+88, [engine+9])` before `009C00C0`. Bit 0x10 off so `+124=0` and `009A6460` does not call sound `00A3EB20`. | `EngineLifecycleTests.First_pump_009F4E20_is_after_empty_PeekMessage` |
| Window / Present from the options record | `00413C50` applies `userst.ini` via `009EC890` before `00403079`. `SetFullscreen` writes `[0x137544A]` (the `009BF7E0` `[ebx+28]` flag; `sete [ebp+572]` Windowed). PE default is 1 (exclusive). `009A64B0` HWND style is always `0xCA0000`. `[0x1375468]` is Z=32, not windowed. `009BEEB0` Present dest is the `009BEF80` viewport over that backbuffer — not a host `0,0,1,1` constant and not a d3d9 `ForceWindowedMode` wrapper. Register site of the `SetFullscreen` *name* is still UNREAD (no `.text` string); dest is the byte `00413C42` / `009BF7E0` already use. | `EngineLifecycleTests.Userst_00413C50_SetFullscreen_false_is_009BF7E0_windowed` |
| `009A57B0` after `009C00C0` | IAT `0x1440378` is `USER32!GetForegroundWindow`, not GetTickCount. Returns `GetForegroundWindow()==[engine+148]` (HWND from `CreateWindowExW` `0x1440388`). First-seen after AVI: window exists and is foreground → 1 so `004162B5` runs vtbl+20/28. Host `GraphicsCreated` / GetTickCount gate is DISPROVEN. | `EngineLifecycleTests.First_pump_009A57B0_is_GetForegroundWindow_eq_hwnd` |
| After `009A57B0` allow | `and [0x13B89A8],0`; vtbl+20 dt `0x13B8690`; `009E9FB0` first-seen 0 → vtbl+28; render dt `0x13B8698`. `00417001` WorldFrame<=1 skips camera but still `00417265` `[0x13B7D6C]=[display+104]`. `004350D0` writes `+104=0`. Host used to return before that copy. | `EngineLifecycleTests.First_pump_00417001_copies_display_plus104_when_WorldFrame_le_1` |
| First-seen `game+164` tick list | Ctor 0. First `0041674A` is 0 so `004AEAA0` does not `009F16F0`. `00418289` skips `0041726D`. Host `SeedWorldTick` queueing type 1 is DISPROVEN. Dispatch table slot 1 `00629270` is separate. | `EngineLifecycleTests.First_pump_0041674A_is_0_so_00418289_skips_00416E78` |
| `0041674A` / `004AEAA0` first-seen | `[game+9]=1`. `0x13B8688` has no `.text` writer (two `cmp` only) so dt path. `004AE9D0` `+9836=[game+72]` ctor 0. `004166E2` startup 0 → `0*15-0 <= 1` → al=0. `004162B5` does not call vtbl+24; `00416E78` only after `004AEBA0` returns 1. Host always-run vtbl+24 is DISPROVEN. | `EngineLifecycleTests.First_pump_0041674A_is_0_so_00418289_skips_00416E78` |
| `004166E2` after `[game+96]` snapshot | `009E1BC0` IAT `0x143FE00` is `KERNEL32!QueryPerformanceCounter` (`0x143FE04` QPF). `004189DC` `fstp [game+96]`. `004166E2` calls `009F7050` (`0x13CB4B0+[0x13CB4F4]*24`); first-seen slot is 0 (`0x122ED70`). Clamp `fcomp` vs `009E1BC0`; `0x13B86A4` has no writer so the clamp stands. Then `fsub [game+96]`. First inner is 0; later inners grow as `009E1BC0-[game+96]`. Host sticky `DisplayTime=0` is DISPROVEN. | `EngineLifecycleTests.Pump_004166E2_is_009E1BC0_minus_game_plus96` |
| `004AEAA0` / `009F16F0` on `0041674A=1` | `inc [esi+9836]`. `009F1720` writes `[game+164]=0`. Packer `[esp+20]=1` then three `009F1760` (+9840/+9844/+9836 before inc). `009F1650` copies 40 bytes into player+8208. `record+0=+9836` after inc. `009F16F0` copies `0x192` dwords (stride `0x648`), count=1. `009F16C0` zeros builder+4. Same `00418289`: `0041726D` `009F1750`/`009F1730`; `[+0]=1>[game+76]=0`; flag `[+76]==[+72]` first-seen 1; `0049DFB0` first walk skips type 1, flag walk `00629270`→`004A5A40`→`004A5E10`. Then `+76=1`, `+72=max`, `004AE9D0` `+9836=+72`. Host note-only append is DISPROVEN. | `EngineLifecycleTests.Pump_004166E2_is_009E1BC0_minus_game_plus96` |
| After `004A5E10` on type-1 tick | `00416670`→`00415FE0`: type 1 `dec;dec` ≠0 so skip game vtbl+16. `00434A60` table type1 `+48` is `[0x13B92F8]=0` (`0121BA4F xor eax`). `00434F60` `[display+232]` first-seen 0 skip. `004A5A40` first-seen `[world+248]=0`/`[world+260]=0` runs `004B4490` then `004A5DF3 006B3FF0` then the inc. `006B3FF0` only other `E8` is `006B42F0`. `00501450` still 0 `E8`/`imm`; `00501935` is inside `00501450`. `00487C20` is persist `PlayerRegionName` (one `E8` `00487F10`, 0 callers of that). Not region load. Host seed-only-after-`00501450` is leftover. | `EngineLifecycleTests.Pump_004166E2_is_009E1BC0_minus_game_plus96` |
| First-seen `004B4490` / `00CB8220` | Empty `[esi+56]` skips `00CB8220`. After Gameflow construct: `00CB7C40` ticks Main then same-walk Core/Barrow (insert-at-tail). `+41=0` → `00A44880`. `00CE7670` state 0 yields on `Q_NewOakValeIntro`. `00CEF3B0` `[+72]=0` yields (no guild `TEXT_QST_078`). `00CEF550` `00892F60`/`004B0FC0` and `00892F40`/`004AF610` miss `Q_TraderConflict*` → yield. `+4=1` `+5=0` → `00CB7950` returns 0 (keep). `006E7410` `0049D870` is after resume, not first yield. `006874B0` still empty. Does not activate Oakvale or trader quests. `00501450` not reached. | `EngineLifecycleTests.Type1_00CB8220_Gameflow_state0_yields_on_Q_NewOakValeIntro` |
| `004B4490` after `00CB8220` skip | `00449970` `[game+28]+28` → `004498C0`. `00487DC0` `add ecx,44; jmp 00A01B50`. Slot ctor `0044BC10` `00A01B10` writes `+48=0`; `0048A210` assigns empty ptr (`00A01B50` of a zeroed temp). First-seen `00A01B50` is 0 so `004AFCA0` (`+145`/`+142`) is skipped. Not `00501450`. | `EngineLifecycleTests.Pump_004166E2_is_009E1BC0_minus_game_plus96` |
| First-seen `006E75C0` | `004A5D91` `ecx=[world+56]` `push ebx`. `world+56` is Init Scripts `006E7740` (world vtbl+28 `004A6550` store `004A6646`, vtbl `0x1260F0C`). Flag=1: thing `00523540` `[+84]=0`, bullet `004C60F0` `+4/+12=0` so `00419680=0`. `vtbl+1580` `0088E9E0` `[this+44]=0`. `vtbl+1544` `00892270` `[0x13B8790]+246=0` (`0049166E` `[gui+24]+222`; no later first-seen writer). `WorldFrame % [0x1375550]` is `0%15==0`. `[this+60]` empty circular (ctor `+8=+12=self`) skips `0059299D`. Not `00501450`. Next is `006874B0([world+96])`. | `EngineLifecycleTests.Pump_004166E2_is_009E1BC0_minus_game_plus96` |
| First-seen `006874B0` | `004B3CE0` construct `E8` `00687540(55,50)` per quest. `[node+64]=50`. `0049D870` is `[0x13B89BC]` WorldFrame; unique inc `004A5E10` is after `006874B0` so now=0 and `50>=0` skips `006872B0`. Empty-list DISPROVEN when quests constructed. After Gameflow `00CB8220` (last in `[esi+56]`). Sunnyvale `00CB8220` first (`00CDD360` yield). `00501450` not reached. | `EngineLifecycleTests.Type1_00CB8220_Gameflow_state0_yields_on_Q_NewOakValeIntro` |
| First-seen 4× `004498C0` / `00640320` | `world+12` is ctor `[arg+8]` (player manager). Slots 0–3 match `+40`. `0048A210`/`0099A350` set `+4=1` and `+534=1` so `0099A330` takes `00488AB0` but skips `004887C0`; `00A01B50(+44)` miss skips `006A4D00`. Then `push ebx` / `00436FB0` / `00640320` (`ret 4` consumes the flag). Singleton `[0x13BA854]`. OnActivate `006404D0` inserted `[engine+44]` (`00B26340` vtbl `012A0F3C`). `vtbl+204` `00B23550` is `[+8]`; ctor `00B260B0` writes 0 so `00B24030` is skipped. `00640490` (WLD) does not insert. Not `00501450`. | `EngineLifecycleTests.Pump_004166E2_is_009E1BC0_minus_game_plus96` |
| After `00640320` on type-1 | `006BB990([world+28], 1/15)`: ctor `+33=0` `+24=0` adds `dt*+16` into `+8/+28`. `006B3FF0`. `004C5E90` is `ret`. `006E60F0` `006E6150` `[+8]=self` empty. `0051F070` `00523540` `+72=+76=0` empty. Then `004A5E10`. `006BDC60` `00487DC0` miss. `0043A080` `+164=0`. `006B2260` `[0x13B8394]=0x01041044` runs; dummy has no `MARKER_POSITIONAL_ATMOS` instance. `006E37D0` `[0x13BABA0]` empty. Not `00501450`. | `EngineLifecycleTests.Type1_00CB8220_Gameflow_state0_yields_on_Q_NewOakValeIntro` |
| `00416E78` body when called | Prefix always: `[world+52].vtbl+4`, `00416392`/`0049E200`, `009F4A90` → input+60/+92, input vtbl+8, `00416F9D 009A57B0`. `0049D870` is WorldFrame **before** `004A5E10`. First type-1 sees 0; second sees 1; both skip `004457F0`. First `WorldFrame>1` is `004457F0` `[+2196]=0` then `00446A30`: `00446330` miss, `00446220` `[+168]=0`, `al=0` so no `0041649C`. Host catchup needs ~`Pump(0.25f)×3` after dummy (`004166E2*15-slot>1`). | `EngineLifecycleTests.After_WorldFrame_gt_1_00416E78_is_004457F0_then_00446A30` |
| First-seen `00417001` WorldFrame>1 | `[0x13B8630]=0` → `0041707E` not `004164E0`. `0049E080`/`006B42F0` (`+68` already 1). `004AEA70`: `+9826=1` so `!0041674A` with post-update `+9836`. Catchup-hit frame (`Pump(0.25f)×2` after dummy) returns 0. `[0x13B8688]=0` → skip `00435F70` / `[+90594]`; inc `[+90596]`. Host always-Present after interpolation is DISPROVEN. `004AEA70 +9826=0 → true` is only the unready player branch. | `EngineLifecycleTests.After_WorldFrame_gt_1_00417001_is_0041707E_then_004AEA70_skip` |
| First later `004AEA70=1` | After skip, `Pump(0)×5` drains `004166E2*15-slot` to `<=1` (`slot=7`, `WF=7`). `00435F70` `push 1` `[+90552]=1`. `00435530` `+232=0` skip `00434CD0`; `009BEF20`/`009D8CF0`/`00435000`/`00435070`/`009D9C80`/`009DA9F0(1)`/`009BEF50`/`009BEEB0`. No region / not `00501450`. Host `PresentToHost` gated on `WorldSubmitted` is DISPROVEN; Present follows `00435F70`. | `EngineLifecycleTests.After_004AEA70_eq_1_00417001_is_00435F70_Present` |
| First-seen `00435530` dest | Ctor `00434E10` `+232=0x1E`; `00434F60` decrements. First Present `+232>0` → `00434CD0`: `+216=0` `[0x1375CDC]=0` skip fade dest, `009D8250` is `ret`. `00435000` `00449960`/`00487DD0` `00A01B50` miss skip `00639E40`. `00435070` `00487DC0` miss skip `0057B43F`. `009DA9F0(1)` `[+16020]==[+16024]` empty → `009DB6E6` no DIP. `00435530` does not `E8` `00B25950`. Host invented layer bits / always-`00639E40` DISPROVEN. | `EngineLifecycleTests.After_004AEA70_eq_1_00417001_is_00435F70_Present` |
| Later type-1 `00CB8220` | `00CB7C40` / `00CB7950`: `+40=0` `+44=0` `00F35A00=1` `+41=0` → `vtbl+4` `00A44880` (not skip). `00A44660` `009D87F0` resumes the `00893610` wait; quest still 0 → `006E7410` yield. Does not re-attach Core/Barrow or re-run tattoo/card. Does not activate `Q_NewOakValeIntro`. Host parked-skip-`00A44880` DISPROVEN. `00CB8170` `[+8]=0` empty. | `EngineLifecycleTests.Type1_resume_00CB8220_is_00A44880_then_00893610_yield` |
| Who activates `Q_NewOakValeIntro` | `00CD6E27` is `00CB5C90` bind `S_QNOVI`/`00DBEF70` only. WLD `+172` is `Q_SunnyvaleMaster` not Oakvale. `+90584` empty skips `004B4A10`. `004B5080` is save `START_NEW_QUEST` (0 external `E8`). `004A113B` `AddTestQuest` stores `[world+196]` only (no `004B2850`/`004B4A10`). `00896A30`/`004B0D30` require `004AF610` already active. `00CE7670` only waits. user.ini is `Gameflow`. Activator is not on this no-save walk. Host must not invent `ActivateQuest(Q_NewOakValeIntro)`. | `EngineLifecycleTests.No_save_does_not_activate_Q_NewOakValeIntro` |
| No-save enqueue `00501450` | Body: player `00449970`/`00487DC0`, `004FEEC0(0,0)` writes `+156=0`, count `(+48-+44)/88` (142). Count>1 loops `00500540(i,0,0)` for i=1..141 (+36 null still `006C27A0`). After each i: `0048D400` (`+145` need `0x0C` forbid `0x21`, `006A80A0` bit `0x64`) then `005198B0`/`00518DC0` `CTCActionUseScriptedHook` — not a release. List occupancy first-seen PARTIAL. Then `RegionGraph.txt` and `00500540(saved=0,0,1)` with no sync pump. Last `004FC8A0` leaves `+156=141` `Filler_NorthernWastes_02`. E8 caller UNREAD. Host second-`Pump` site is DISPROVEN. Invented “only index 1” is DISPROVEN. | `EngineLifecycleTests.Second_pump_00501450_is_004FEEC0_then_00500540_1_0_0` |
| `006C2170` apply order | `006C27A0` copies maps (`006C2D40` stride 28) and sets `+28=index`; `006C2120` enqueue; sync `006C20A0` → `006C2710` → `006C2170`. Apply is topology (`004FF080`/`004FF440`) then objects (`00522720`/`00521AE0`) then `0051E2F0` then `004FD020` then `004FCBB0`/`004FCFE0` then vtbl+88/`004FC8A0`. Host `004FCBB0`-before-objects is DISPROVEN. | `EngineLifecycleTests.Apply_006C2170_is_topology_then_objects_then_004FCBB0` |
| `004FC8A0` / `00500230` | First-seen job `+12=0` so `00500230`/`0050AF10` skip. `006C2170` calls `005064C0` (vtbl+88) **before** `004FC8A0`. `004FC8A0` writes `+156`, `00437CE0`, `0082BA00` only — not villages and not `00B428E0`. Then `004AFC00`. `00500540` after that is list dtor / `ret 12`. | `EngineLifecycleTests.SetRegionAsLoaded_004FC8A0_is_minimap_after_005064C0` |
| `00B428E0` caller | No `E8` / no stored dword. Reached via display `012A0F3C+208` thunk `00B23DC0` (`mov ecx,[0x1436E8C]; jmp 00B428E0`). First-seen site is `004A1840` `004A1BD3`, arg `0049DDD0` `Data\Levels\FinalAlbion.stb` (`[0x13B8616]==0`). TLC has only `FinalAlbion_RT.stb`, so `[+52].vtbl+12` misses and `00B42750` does not write `+424` / `00B420F0`. Host open after `004AFC00` is DISPROVEN. | `EngineLifecycleTests.LoadWorld_004A1840_set_static_map_is_00B23DC0_then_00B428E0` |
| Install | TLC `data\` is the source of truth. Anniversary FableData is a fallback. | `TlcInstallTests` |
| WLD | Text region graph. Map 1 is Lookout Point at (3232, 3488). | `TlcInstallTests` |
| WAD / BBB | `FinalAlbion.wad` is a BBB bank of `.lev` / `.tng` / etc. `Find` requires the extension. | `TlcInstallTests` |
| TNG | Version-2 text. Things have `DefinitionType` plus `CTCPhysicsStandard` position and axes. Lookout has ~288 things. | `TlcInstallTests` |
| QST | Quest table includes `Q_SunnyvaleMaster`. | `TlcInstallTests` |
| names.bin | 20-byte header, then `(u32 hash, cstring)` pairs. Lists `MARKER_BASIC` and Oakvale names. | `TlcInstallTests` |
| BIGB | `graphics.big` / `textures.big` have named sub-banks. Mesh bank ids match `meshdata.h`. | `TlcInstallTests` |
| LZO | Fable framing: `u16` size, `0xFFFF` + `u32` for large blocks, last 3 bytes raw. | mesh / texture tests |
| C3D mesh | Positions parse. Packed `POSPACKED3` + scale/offset. Mesh space is centimetres (`* 0.01` to WLD). Apple tree id 5228 has 1699 tris. | `WorldGeometryTests`, `MeshFormatTests` |
| C3D materials | After the root matrix: `id`, name, `DecalID`, **`DiffuseMapID`**, bump / reflection / illumination, `MapFlags`, `SelfIllumination`, then **4 flag bytes**. Serialize `00ABF6B0` stores them at **+40..+43** (stride **48**). House `DegenerateTriangles` (diffuse 0) is **Flag3=1**. Kid hair and house mat 3180 are **Flag1=1** (`C3DMeshMaterial+41`). First-seen landscape / static-lit apply **CCW** with no Flag1 test; NONE passes apply `0x01396FB8` unconditionally and restore CCW after the draw. **Flag1 is not the first-seen NONE selector** (`FirstSeenAppliesCullNoneFromFlag1=false`). First-seen PALSKIN draw **does** read +41/+42 (`FirstSeenPalskinReadsFlag1=true`): opacity `0xFF` skips the `[inst+12]` bit-9 fill; Flag2→`or ebx, 2`; else Flag1→`or ebx, 5`. That bitfield plus MapFlags `[mat+32]` picks helper type index (hair MapFlags=1 → **4**). First-seen static-lit `00BB2540` does not read Flag1 (`FirstSeenStaticLitReadsFlag1=false`). Bind pass 4 still does not consume helper+28. | `MeshFormatTests`, `ScenePassTests` |
| C3D UVs | After packed pos + packed normal. Packed UV is `int16 / 2048 - 8`. Stride-12 static verts put UV at byte 8. Values sit around 0–1 with some tiling. | `MeshFormatTests` |
| textures.big | 34-byte info: `u16` w/h at 0/2, frame w/h at 6/8, format at 12. **31 = DXT1**, **32 = DXT5**, **35 = DXT5 16-byte block** (sky only). Type 4 / format 1 = RGBA8. First framed LZO stream is the **top mip** (expected = `TopMipSize`, not the full chain). First-seen 512 DXT1 leftover after that stream is **43688** raw bytes = mips **256..4** (`PayloadHasRawLowerMips`); no 2×2/1×1. | `TextureFormatTests` |
| DiffuseMapID | Mesh material `DiffuseMapID` **is** the `textures.big` entry id. 3880 = oak trunk, 2119 = apple leaves, 414 = `LANDSCAPE_GRASS_PLAIN`. | `MeshFormatTests` |
| WAD `.lev` | Version 25, constant `0x1904`. 255×132 material table from 179 (`INVALID_THEME_STANDIN`, then `GROUND_*`). Sound themes after 67639. Payload starts with `21`. This file is a material/theme table, **not** a C3D mesh. | `LevFormatTests` |
| STB coarse height | Runtime `FinalAlbion_RT.stb` copy of `.lev` is ~3 MB. Bytes 0–2047 are pad (`u32[0]=1`). From 2056, 36-byte records hold two WLD-space verts on a **16-unit** lattice. Lookout is 8×8 quads, Picnic 8×6. Heights 20–80 match TNG Z. | `LevFormatTests` |
| STB section 2 offset | `u32` at 2048 is the start of the next blob (Lookout/OakVale **6144**, Picnic **4096**). Zeros fill the gap after the coarse lattice. | `LevFormatTests` |
| WAD cell table | Payload after the `21` tag is a stream of **21-byte** records, one per 1-unit cell (Lookout 16384). Bytes 10–13 are material-table slots (`0xFF` unused). Lookout is mostly slot 2 `GROUND_PATH_SAND`. | `LevFormatTests` |
| GROUND_ → LANDSCAPE_ | Material-slot `u32` id is **not** a `textures.big` id (1911 is villager legs). Names map: `GROUND_PATH_SAND` → `LANDSCAPE_PATH_SAND_01` (4133), `GROUND_GRASS` → `LANDSCAPE_GRASS_PLAIN` (414), `PATH_COBBLES_IRREGULAR_ET` → `LANDSCAPE_COBBLES_IRREGULAR_01` (4118). | `LevFormatTests` |
| Fine terrain mesh | 1-unit quads still exist for sampling. The **drawn** landscape is the STB tile mesh: index **strip** for adaptive tiles, 17×17 quads only when `v=289` and the lattice is complete. Treating 70%-filled adaptive tiles as a grid skipped ~1120 Lookout quads (sky holes). Draw bit `0x40` walks patches through **`00BDC2D0`**: 4-plane AABB vs camera `+448` (stride 16). `n[i]&gt;0` picks patch `+168` (min) else `+180` (max); reject when `n·p &gt; d` (fully outside). Setup `00B30B50` writes cot half-FOV at +212/+216 (`0x122F59C=0.5`; +84 clear letterbox `0x1238174=0.75`) and inverts the cot-scaled view 3x4 at +228. Extract `00B2FD60` (only caller `00B3110F`, arg1≠0) unprojects eye + NDC `(±1,±1,1)` then `n=(Pnext-Eye)×(Pprev-Eye)`, normalize `00A14440` (`0x122DED8=1`), store `00A42140` at +448/+464/+480/+496. `d=n·Eye`. Missing AABB submits every cell. Patch AABB **fill** is `00BF6F80` (only `E8` from `00BDC280` / setup `00BDC180`): first-seen start `push 0,0`, size map `+92/+94`, origin map `+96/+98`, **`min.z=max.z=0`**. Tile-triangle boxes were a stand-in — removed. Getter `00BDBFC0` is `&this+168` only. First-seen SHOT2 is `CAMERA_POINT_SCRIPTED_SPLINE` with TNG FOV **0.2 turns** (`0.2*360=72`). Camera ctor `00B31700` zeros +536 so update `00B314E0` takes the helper path: `00A0BE80` `[helper+40]` bit **2** selects two FOV; SHOT2 has a single FOV field so **+84 is clear**. FOV H is `00A0BE90` `[helper+44] * 360 * 1/360 * 2π` (`0x1238020` / `0x1238E00` / `0x128F608`). Spline update `00B31160` (if +536 set by `00B2FC10`) writes +84=**0** and copies key+24; `00B2FC10` has zero static xrefs. +84-clear letterbox `0.75` at 4:3 leaves horizontal FOV unchanged. `00A3A7F0` +84 is a stream-version flag, not this byte. | `LevFormatTests`, `WorldSceneTests`, `CameraProjectionTests` |
| GPU texturing | Mesh vertex is pos/normal/UV. Draws are grouped by `textures.big` id. RGBA is uploaded as `R8G8B8A8` with a repeat/linear sampler. Fragment shader samples `albedo`. Some bank ids fail framed LZO and fall back to a 1×1. | `GpuTextureTests` |
| STB tile table | After the coarse 36-byte lattice, leftover `magic/offset/size` is a tile directory (`0x012EC900`, file offset, packed size). Lookout 63 tiles, Picnic 47. Last record is a 0,0 sentinel. | `LevFormatTests` |
| STB tile payload | `u32` uncompressed size, `u32` packed size, then **raw LZO** (not framed). Inflated 32-byte header: `u16` extra-object count, `u16` vert count at +2 (289 = 17×17), `u16` strip length at +4, `u16` flag at +18 (`256` = implicit 17×17, no primary strip). | `LevFormatTests` |
| STB tile verts | 32-byte header, then `count` × **15-byte** records: `u16` world X, `u16` world Y, `f32` Z, 7 unread bytes (packed normal / colour). Mesh origin is the coarse cell's **east** edge. | `LevFormatTests` |
| STB section 2 tile | The blob at `u32@2048` is the **same** raw-LZO tile. Lookout covers WLD `[3232,3248]×[3488,3504]` (map origin, 289 verts). Picnic covers `[3104,3120]×[3520,3536]`. This is the west-south cell the table does not store. | `LevFormatTests` |
| STB tile indices | After the vertex array, adaptive tiles store a D3D triangle strip. Header `u16@+4` is **PrimitiveCount**; IndexCount is that plus 2. Treating it as an index count dropped the last 1 m triangle of every strip (Lookout area ≈ 1). | `LevFormatTests` |
| STB edge-strip objects | After the primary strip (or immediately when flag=256) sit `CPatchTesselationEdgeStrip` blobs. 30-byte header `v, primitiveCount, fmt…` then 15-byte world verts + PrimitiveCount+2 indices. Lookout tiles carry 3–16 extras; they fill the ~11% of 1 m cells the primary strip does not cover. | `LevFormatTests` |
| Camera | System.Numerics row-major is uploaded as-is (no extra transpose). Z-up. Overview `(64,-40,95)` looks at `(64,64,36)`. First-seen is **not** `CView3DThirdPerson` follow-cam. Update `00B314E0` (`+536==0`) copies helper `+0/+12/+24` as pos/look/up, builds `right=up×look`, packs `(right, look, up)` at +16 (`FirstSeenViewUsesCreateLookAt=false`), then `00B30B50`. `00B2FBF0` only stores the helper pointer at camera `+12`. `00B23B50` (vtbl `0x012A0F4C` +44) binds that source with **`push 1`**. That is `00B30B50` arg2 so **`00B2FC50` runs**: `00988350(camera+128)` view (cot-scaled 3x4), `00988540(camera+372)` proj, `009881F0` identity world. Pre-pass `00B27994 push 0` skips it. Sky `00B66A01` calls `00B2FC50` on `[0x1436EA0]` again. Per-cell `00BF46A2` then rebuilds world as identity 3x4 plus **`[0x1436EA0]+84/+88/+92`** (helper pos after the +20 copy) and `009881F0`s it. `00988A50` then `009883F0` product is **`p * W * V * P`** (`FirstSeenWvpIsWorldViewProj=true`). `009883F0` is XY identity (`FirstSeenProjXyIsIdentity=true`); cot is on +128. Live props/sky use identity world; landscape bits **4 / 0x40** use `T(cam)` on a **camera-relative** VB. Host STB tiles are **world-space** (`LevTileMesh`); `p_world*T(cam)` is DISPROVEN (`HostTcamOnWorldSpaceLandscapeIsDisproven`). Submit uses identity world (`HostLandscapeViewProjection`) — equivalent to `(p-cam)*T(cam)`. Applying `T(cam)` in the shared VP would move the house. 7000 remains SKY_DEF far. | `WorldSceneTests`, `CameraProjectionTests`, exe `00B23B50` / `00B2FC50` / `00BF46A2` / `00988350` / `00988A50` |
| Object basis | C3D is Z-up centimetres. TNG `RHSetForward` / `RHSetUp` are a right-handed Z-up frame (mesh X=right, Y=forward, Z=up). Streetlamp 4978 is 345 units tall in **Z**. | `WorldGeometryTests` |
| Region space | One region is drawn in **local** metres: TNG XY is 0–128 on Lookout, not WLD `(3232,3488)`. STB world XY is subtracted by MapX/MapY. Object Z matches fine terrain (median ~10 cm). `ObjectScale` multiplies the 0.01 mesh scale. | `WorldGeometryTests` |
| TNG → mesh (partial) | `meshdata.h` name match: `DefinitionType`, `MESH_` + type, or `MESH_` + stem after `OBJECT_` / `CREATURE_` / `BUILDING_`. Skip `[PHYSICS]`. Still useful as a fallback. | `MeshFormatTests` |
| names.bin offsets | Each name is `(u32 CRC, cstring)`. The game.bin name-ref is the string's offset after the 20-byte header (first string is 4). CRC is Fable polynomial `0xEDB88320` init 0. | `GameBinFormatTests` |
| game.bin container | 13-byte header: `u8 use_names_bin` (TLC = 0), `u32 file`, `u32 platform`, `u32 entry_count` (14761). Then `NameRef` × N (`type_off`, `file_off`, `counter`). Then chunk index + **zlib level 1** chunks (`78 01`). | `GameBinFormatTests` |
| game.bin OBJECT → mesh | Instance name is the `file_off` names.bin string (`OBJECT_WALL_SMALL_POST_01`). Body starts with a 3-byte preamble, then a sub-def table, then control fields. Field CRC `"Graphic"` is followed by `EngineGraphic` (`type i32`, **`bank_index i32`** = `graphics.big` mesh id). | `GameBinFormatTests` |
| Lookout TNG → mesh | Walls 5331 `MESH_SMALL_WALL_CURVED_POST_01`, Brightwood rocks 7828 `MESH_MEDIUMROCK_LICHEN_01`, streetlamps 4978 `MESH_OBJECT_STREETLAMP_OFF_02`, pillars 7168, thorn vines 3977, villager 5149. Lookout instances **> 150**. Markers/cameras stay gizmos. | `GameBinFormatTests`, `WorldGeometryTests` |
| frontend.bin / script.bin | Same GameBin container as game.bin (13-byte header, platform `0xA8E36C34`). Frontend is 810 entries, mostly `UI`. Script is 611 entries, mostly `CCutsceneDef` (`CS_ATTRACT_1`, …). | `DataCatalogTests` |
| FinalAlbion.bwd | Region index: 398 records matching every WLD map. Record = path + name + 3 flag bytes + `u32` min/max X/Y (Lookout `3232,3488`–`3360,3616`) + 9 bytes whose `u32` at +1 is the WLD **MapUID** (Lookout 162441). Declared count is 399; leftover after 398 is a second unread blob. | `DataCatalogTests`, `WorldSceneTests` |
| Starting region graph | `Misc\FinalAlbion_StartingRegionGraph.txt` is `"Region": "Neighbour", …`. Lookout lists Picnic, BowerstoneSlums, GreatwoodEntrance, guild, demon door. Picnic AABB shares Lookout's west edge. | `WorldSceneTests` |
| TNG region exits | Lookout has `REGION_ENTRANCE_POINT`, `REGION_EXIT_POINT`, `OBJECT_REGION_TRANSITION_GATE`. `EntranceConnectedToUID` packs `(MapUID << 40) \| entranceSlot`. Slot is the low 32 bits of the dest `REGION_ENTRANCE_POINT.UID` (`0xFFFFFE00_00000000 \| slot`). Lookout→Picnic is slot `0x21` at local (79.6, 55.6). ≥120 WAD exits resolve. | `WorldSceneTests` |
| First-scene runtime | Registrar `00CD6E27` binds **`Q_NewOakValeIntro`** to script **`S_QNOVI`** and factory **`00DBEF70`**. VM list is 24-byte records: bind `00CB5C90` / store `00CB7210` / start `00CB7780` → `00CB70E0` → walk `00CB6EA0` → per-item `00CB6CE0`. Slot 2 is `[vtbl+8]` (base `00CBD4C0` is `ret`; S_QNOVI overrides `00DABAC0`). Yield is context `vtbl+28` (14 sites in StartOakVale). 12 s wait is `vtbl+2592(1,&+76)` then **`vtbl+2584(12.0)`** (`00DBE128`); that call is the blocking wait. After it returns the fn looks up `HerosOldHouse` and spins on **`+80`**. `+80=1` scans in `00CB–00DC` are later quests (`Q_AwakeningTheOracle`, `Gate3Inner`), not this wait. Live update is microthread **`00A44880`** (dt `009E1BC0` → `+8`, resume `00A44660`). Fiber **`00A446A0`** calls persist **`00DAADA0`** (`004045C0` name **`AttackOver`** at `this+80`) then run `00DABAC0`. Yield is **`00A44690`**. Save/load of first-seen is that `AttackOver` field — `ScriptRuntime.ApplyPersist` / `Update` do **not** invent a 12 s `+80` writer. Live path is generic **`ScriptBank`** + **`ScriptInterpreter`** (`00CBFB7D`) + **`ScriptRuntime`** (name table `00CB8230` / thing activate `004C97B0` / microthread `00A44880`). `NewGameScript` is observation only — it does not assert `CutsceneStarted`/`PlayMusicRan`/`FadeOutReached`. `CCutsceneDef` persist `00F2A1D0` reads **8** CString vectors (`004331F9` / `00433273`: skip-u32 + count + NUL strings) after a 5-byte GameBin prefix. `00CBFB7D` copies vector **0** at runtime `+60` (`00432EE9`, count `(end-begin)>>2`). `ScriptBank` parses that layout; printable scrape is discovery only. First slice: TNG `NOVI_LiveFather` activate → named start → `CS_OAKVALE_INTRO_FATHER` → PlayMusic → `FadeOut 0.5,0` → `CameraPause` → `.Teleport` `00CC4678` (no `vtbl+28`, `jmp 00CC707C`) → `Father.LookToThing` apply then **`vtbl+28`** (`[ebp+103]=1` at `00CBFC65`) → `DoScriptFrame 1` (`00CC7085`: default count 1, `0099E7F0` atoi, one `vtbl+28` per count) via `00A44880`/`00A44660` resume → `DoCameraPreloading` `00CC86D0` (`vtbl+1564`, `00CBF29F` dl=0, `vtbl+1568`, **`jmp 00CD17FD`**, no yield; first-seen has no TRUE arg so `vtbl+1560` is skipped) → second `DoScriptFrame 1` → `PlayAVI` `00CCA26D` (prefix `Data\Video\`, `vtbl+1476`, `jmp 00CD17F8`, no interpreter yield; apply body UNREAD) → `MuteSounds false` `00CC7258` (`00CBEE0C` → `vtbl+2664(0)`, `jmp 00CC8464`, no yield) → second `FadeOut 0.5,0` → yield on `DoScriptFrame 2` → two more resumes → `Hero.PlayAnimation CS_WAKING_UP_LOOP` (`00CC14B8` `vtbl+72` flags `(0,0,1,0,0)`, `[ebp-22]=1` one `vtbl+28` then `00CC7081`) → `CS_WAKING_UP_ON_STEPS` (same) → yield on `DoScriptFrame 4` → `StartTimeCode` (`00CD1373` `and [0x13B83C8],0`, `jmp 00CD17FD`) → `PlayMusic MUSIC_SET_OAKVALE` → `NoLoadUseCamera CAM_OVI_ID_STANDUP` → `FadeIn` → `GamePause 1.6` (15× scaled frames, not dt) → `Father.Speak` (Father stubs: one `vtbl+28`) → `GamePause 1.0` → `Father.InteractiveSpeak` (FALSE → one `vtbl+28`) → `GamePause 1.2` → `UseCamera CAM_OVIF_SHOT2` (name bind + one `vtbl+28`) → yield on `GamePause 5.2`. `vtbl+72` apply body UNREAD. The same interpreter runs any other `CCutsceneDef`. Text-script hooks (`00CBF29F` `UseCamera`/`CameraLookAt`, `00CC14B9` `.PlayAnimation`, `00CC4B22` `.FadeIn`/`.FadeOut`) are the general opcode matcher; their `E8` callers are the dispatcher (`00CBFE3B` / `00CD0DB2`), **not** `00DBDE40` (`FirstSeenCallsUseCamera=false`, `FirstSeenCallsPlayAnimationDispatcher=false`). `script.bin` has no `S_QNOVI` entry (`FirstSeenScriptBinHasSqnovi=false`) — first-seen quest object is native. Bank **does** have `CS_OAKVALE_INTRO_FATHER` (`UseCamera CAM_OVIF_SHOT2`, `FadeOut`/`FadeIn`, `Hero.PlayAnimation`). Xref `00DB88DE` is **`00DB86B0`**, not dtor `00DB8680` (microthread vtbl `0x012D95B0`). `00DB86B0` looks up Hero/Father then **`00CBFB7D("CS_OAKVALE_INTRO_FATHER")`**. Interpreter `00CBFB7D` special-cases `FadeOut 0.5,0` (`vtbl+1488`) then **`00CBF29F`** preloads `UseCamera` names via **`vtbl+1648`**. Activate is **`00CC9F3A`**: lookup TNG by name, bind **`vtbl+1656`** (thing) or **`vtbl+1648`** (name). First-seen reach is **`NOVI_LiveFather`**: `00DABAC0` registers that name with factory **`00DAC2C0`** at record `+16` (name-record vtbl **`0x012D8370`**) before `00DBDE40` map-wait. StartOakValeWest TNG has **`CREATURE_HERO_FATHER` / `NOVI_LiveFather`**. Construct **`004C97B0`** → **`00CB8960`** → **`00DB8520`** → **`00DAC2C0`** writes LiveFather vtbl **`0x012D8388`** (`[0]` dtor `00DAC370`, `[1]` run `00DB86B0`). Fiber persist **`00DB8630`** calls `[+52].vtbl+4`. Activate **`004C7CF0`** → **`004AFB00`** → **`00CB88B0`** is the same name match. `FirstSeenStartsIntroCutscene=true`. `00CBFB7D` at `00CBFD95`: `[ebp+120]!=1` (00DB86B0 `push 1` then three `push ebp` with ebp=0) takes def+60 and compares the first line to **`FadeOut 0.5,0`**, then `vtbl+1488(0.5, 0)` (`0x122F59C`). Bank first line is **`PlayMusic MUSIC_SET_NULL`**, so that compare misses (`00CBFE31`) (`FirstSeenFadeSpecialCaseRuns=false`). PlayAVI is later in the same interpreter (`00CCA26E`, prefix `Data\Video\`, `vtbl+1476`). Wake is `.PlayAnimation CS_WAKING_UP_*`. Neither is first-command (`FirstSeenPlayAvi=false`, `FirstSeenFadeOpcodeInStartOakVale=true`). Do not invent fade/AVI/wake playback. Live camera is TNG **`CAM_OVIF_SHOT2`** on `ScriptedCamera`; FlyCamera is F2 debug only and does not write that object. Exported by `export-scripts` → `script-bank/`. `Registering Scripts` `00CB5D80` → `00CD52D0` is the quest table.  Factory allocs **`0x10C`** and calls ctor **`00DAAC00`** (vtbl **`0x12D7A28`**, context `+64`). Slot **0** `00DBEFA0` is the dtor. Slot **1** `00DAACE0` builds the **`Main`** watcher (`00CDD450`, callback `00CDD440`) — **not** a frame tick. Slot **2** `00DABAC0` registers `NOVI_*` names then **`E8 00DBDE40`** (only caller `00DAC295`). Slot **3** `00DAADD0` **clears `+80`**. Zero `E8` of slot 2 — the script VM calls `[vtbl+8]`. `00DBDE40` yields on `[this+64].vtbl+28` and waits `[this+80]` after pushing **12.0f** to context **vtbl+2584**. No `mov [esi+80]` in `00DBDE00–00DBF000` (`FirstSeenPlus80WrittenInStartOakVale=false`). Render submit is **`00B25950`**, not the quest object. Camera update `00B314E0` has **2** `E8` callers (`00B23B6C` bind, `00B2799D` pre-pass) and does **not** name `CAM_OVIF_*` (`FirstSeenCameraNameInExe=false`). `FadeOut` / `StayFadedOut` are script-dispatcher string compares (`00CD096F` / `00CD087E`); first-seen `00DBDE40` does not call them (`FirstSeenFadeOpcodeInStartOakVale=true`). `WatchBarrels` `00DBE890` looks up `NOVI_Barrel` and waits on `+80`/`+116`; beetle/gold only after smash count (`FirstSeenWatchBarrelsSpawnsBeetle=false`). Kid create still has no PlayAnimation (`FirstSeenPlaysAnim=false`). After `+80` the same fn calls PostAttack `00DBE3C0` — not first-seen player control (`FirstSeenHandsPlayerControl=false`). | `WorldSceneTests`, exe `newgame-trace` v80 |
| Player start + walk | **New game is not Lookout.** Click path: `UI_TEXT_NEW_GAME` `00595B24` → `START_NEW_QUEST` → `Q_NewOakValeIntro` / `StartOakVale` `00DBDE40`. `00CB7940` is a 6-insn **hero-exists** predicate (`[this+44]` then `[hero+5]`), not spawn/pose. `004AA840` is a string destructor (vtbl `0x1238C8C` → `0099A2E0`), not a creature ctor. After `CREATURE_HERO_CHILD` the same fn constructs three 60-byte watchers via `00CDD450` (`0.1f` / 64 / 1, vtbl `0x12D7A3C`): **`WatchBarrels`** callback **`00DBE890`** (first-seen), **`WatchForGotGold`** `00DBE2E0`, **`ManageQuestCoreMarkers`** `00DBE4E0` (`NOVI_*` later intro — not first-seen, do not follow off StartOakVale). Then `Q_NewOakValeIntro_PreAttack`, writes **12.0f** (`0x41400000`) at vtbl+2584, looks up **`HerosOldHouse`**, then waits on `[this+80]`. `00DBE3C0` / `00DBEB20` are **post-attack** (`Q__OakValeIntro_PostAttack`, `M_PostAttackStart`) — not first-seen. First-seen is **inside HerosOldHouse** (mesh **6909**, **2** prims: walls **345** + **3180**; roof 3182 / floors 3184 have no prims). Indoor TNG also emits fireplace / door / chairs / cupboards / bookshelves / extra beds / table-lamp (Graphic CRC on every def type, not only OBJECT/CREATURE/BUILDING). Ceiling `MARKER_LIGHT` at (33.91,131.55,22.35): `CTCLight` Active, colour **(130,60,5,255)**, inner **8** / outer **9**. Camera `CAM_OVIF_SHOT2` spline key 0 (FOV **72**). `Diffuse2X` `0098B5E0` is dirty bits, not CULLMODE. First-seen landscape / static-lit apply **D3DCULL_CCW** (`0x01396FB0` = **3**) onto device slot `+10384` (`D3DRS_CULLMODE` `0x16`). Flush is `00A044E0` `SetRenderState` (`[dev+228]`). Vulkan mesh pipeline culls back faces (`FrontFace` CCW; projection `M22` is flipped for Vulkan NDC). `0x01396FB8` = 1 (NONE) is applied unconditionally in other primitive passes (`00B89C30` / `00BBE090` / `00BC3F30`) then restored to CCW after the draw — not selected from C3D Flag1 (`FirstSeenAppliesCullNoneFromFlag1=false`). Create `006AC910` → ConstructFromParams `006A9DD0` → parent `00662880` / `008388D0` / `006A5950` / activate `004C9CA0` have **no** PlayAnimation / STAND / CTCIdle call. `STAND` has zero code xrefs. `.PlayAnimation` is script dispatcher `00CBFACA`, not first-seen create. **`FirstSeenPlaysAnim=false`**. D3D Z **LESSEQUAL**. | `WorldSceneTests`, `WorldGeometryTests`, `MeshFormatTests`, `GameBinFormatTests`, `ScenePassTests`, `ShaderFormatTests`, exe `newgame-trace` v29 |
| Hero age stages | Three listed stages, two meshes. **Kid:** `CREATURE_HERO_CHILD` / `CREATURE_YOUNG_HERO` → graphics.big **4300** `MESH_YOUNGHERO_02`; bones `hero_young_set.bncfg` (`CREATURE_HERO_CHILD_02`); quests `Q_NewOakValeIntro` / `_PreAttack`. **Tween (Guild):** `CREATURE_HERO_TRAINING` → **4299** `MESH_HERO` (same as adult) + `CHeroMorphDef` persist `Teenager` (`0071D102`) + `hero_teen_set.bncfg`; quests `Q_GuildTraining*`; HSPs `GuildArrivalHSP` (Lookout 52.7, 69.6) then `GuildTrainingHSP` (HeroGuildComplexInside 57.6, 126.0). **Adult:** `CREATURE_HERO` → **4299** `MESH_HERO`; WLD initial `Q_SunnyvaleMaster`; Lookout graph + adult Oakvale `OakValeWest_v2` / `OakValeEast_v2`. Morph persist also `Strength` / `Will` / `Skill` / `Morality` / `Fatness` (`0071D037`). No third creature id. | `WorldSceneTests`, `GameBinFormatTests`, `DataCatalogTests`, exe `CHeroMorphDef` |
| BWD display names | After the 398 AABB records: ~94 overworld blocks of 4 strings (script, `TXT_REGION_*`, `REGION_*`, `MINIMAP_*`). **72** of those script names are WLD maps (`MapUIDCount`). Extra starts `01 01 01` + `f32` scale (Lookout 1.0) + four `i32`s; the last two are minimap XY (Picnic X < Lookout X). Then length-prefixed neighbour names (Lookout lists PicnicArea and HeroGuildComplexInside). | `WorldSceneTests` |
| GTG kick points | `REGION_KICK_TO_POINT.ScriptData` is a region name (`BowerstonePosh` / `BowerstoneSlums`). | `WorldSceneTests` |
| GlobalQuests.qst | Same `AddQuest` text as the master table. Includes `Global_WatchForHeroDeath`. | `WorldSceneTests` |
| FinalAlbion.gtg | Version-2 thing text (`NEWMAP 1`), not a .lev. Parses with the TNG reader. Global `REGION_ENTRANCE_POINT` / `HOLY_SITE_PLAYER_START`. | `DataCatalogTests` |
| .bncfg | Text bone morphs. `Creature_type: CREATURE_HERO` / `CREATURE_BS_VILLAGER_MALE`, then `Bip01 *` XYZ scales and named bone groups. 60 files in `data\Bones`. | `DataCatalogTests` |
| text.big | BIGB bank `TEXT_ENGLISH_MAIN`, 28913 UTF-16 LE strings. Id 1 is flourish on-screen help. | `DataCatalogTests` |
| Other BIGB | `fonts.big`, `dialogue.big` (lipsync), `frontend.big`, `effects.big` (particles), `shaders.big` (pixel/vertex programs). Same BIGB footer as graphics/textures. | `DataCatalogTests` |
| Sound .lug / Dialogue.lut | Lionhead audio, **not** BIGB. Magic `LiOnHeAd` + `LHFileSegmentBankInfo` / `LHAudioBankCompData`. `.met` is a sidecar (u32=1 then source WAV path). | `DataCatalogTests` |
| stars.dat | `u32` count **1330**, then 24-byte records (6 floats). | `DataCatalogTests` |
| Fable.exe world load | RTTI: `CWorld` / `CWorldMap` / `CLevelLoader` / `CEngineLandscapeMap` / `CEngineLandscapePatch` / `CLandscapeBackgroundPatch` / `CLandscapePatchTesselator` / `CPatchTesselationEdgeStrip` / `CStaticMapBankFile` / `CHeightMap` / `CEngineWaterRenderer`. `SetStaticMapFileForUse` logs **`OpenStaticMaps`** (plural), `OpenStaticMap`, `LoadWaterData`, `CloseStaticMapFile`. Region files then `Activate Topology` / `Post Load Initialise`. WLD tokens `NewMap` / `MapX` / `LoadedOnPlayerProximity TRUE;` are parsed as written. | exe string dump 2026-08-16 |
| AABB static maps | `OpenStaticMaps` + `CLandscapeBackgroundPatch` load **BWD rectangles that touch**, not the starting-region teleport graph. Lookout (3232,3488–3360,3616) touches Picnic (−128,+32), Bridge (0,+128), GuildExterior (+128,−32), Greatwood_1 (+32,−192), Greatwood_2 (−64,−192), plus Picnic fillers. Those five named maps are `LoadedOnPlayerProximity TRUE`; picnic fillers are FALSE but still have STB `.lev` (FindLev used to skip `Filler` by name — invented). Graph names (`BowerstoneSlums`, `GreatwoodEntrance`) are exits, not tiles. | `WorldSceneTests`, `WorldGeometryTests` |
| Active vs nearby map | Exe `OpenStaticMaps` `00B42750` has two modes (`cmp arg, 2`): mode 2 calls `OpenStaticMap` with `push 2`; `SetStaticMapFileForUse` uses mode 1 (`push 1`) then `OpenStaticMap` with that flag. `CEngineLandscapePatch` (vtbl `0x012A8200`, ctor writes at `00BF3A16`) is the current map; `CLandscapeBackgroundPatch` (vtbl `0x012A803C`, ctor `00BE6090`) is the neighbour. `Activate Topology` `004FCBB0` sets a 72-byte map-record flag at `+38` to 1 (current only). Nearby GuildExterior TNG instances `BUILDING_GUILD_LO_POLY_01` (mesh 5949). The high-poly `BUILDING_GUILD_EXTERIOR_01/02/05/06` live in `HeroGuildComplexInside.tng` (MapX 4576, not AABB-adjacent to Lookout). | `WorldSceneTests`, exe `00B42750` / `004FCBB0` |
| Neighbour scene | Lookout world mesh is local metres **plus** AABB neighbours **and** WLD `NewRegion` `ContainsMap`/`SeesMap`. Scene spans X&lt;0 and X&gt;128, Y&lt;0 and Y&gt;128. Picnic/Lookout fillers that have STB `.lev` now emit. Instances &gt; 192. | `WorldGeometryTests.Lookout_scene_opens_aabb_adjacent_static_maps` |
| WLD `NewRegion` | Token switch at `00507FA0` (`rep cmps` length 0xA). Writer `004FD040` emits `NewRegion `, `RegionName "`, `ContainsMap "`, `SeesMap "`. New-game region 4 `StartOakVale` (`TXT_REGION_OAKVALE` / `REGION_OAK_VALE_INTRO`) **Contains** West / East / MemorialGarden and **Sees** five fillers + four seas. AABB-from-West misses `Filler_04` / `Sea_01` / `Sea_02` (they touch the west filler, not West). `FindLev` must not skip `Filler` — those STB blobs are 0.6–2.7 MB. | `WorldSceneTests`, `WorldGeometryTests.New_game_oakvale_loads_contains_and_sees_maps`, exe `004FD040` / `00507FD2` |
| Fable.exe world draw | `CRenderManager` owns `CEngineSkyRenderer` → `CEngineLandscapeRenderer` / `CLandscapeBackgroundPatch` → `CEnginePrimitiveManagerStaticMeshes` (`VSHADER_STATIC_DIRLIGHT_FOG`) → `CEngineWaterRenderer` → weather / local detail / particles / HUD. State `CEngineStateBlockDiffuse2X`. Lighting: `CEngineLightingManager` + `CShaderDirectionalLight` + `CLightDef` (47, BGRA colour + Inner/OuterRadius). Sky: `CSkyDef` `SKY_DEF`. | exe RTTI 2026-08-16, `ShaderFormatTests` |
| SKY_DEF | game.bin `SKY` / `SKY_DEF`. CRC fields: `SunTexture`=384 `GRAPHIC_ATMOSPHERIC_SUN`, `StarTexture`=401 `GRAPHIC_ATMOSPHERIC_STAR_01`, then flare `Texture` 393–399 (`LENSFLARE_01`–`07`) with `Radius` 500–6000 and `Position` −1.4..1. Time-of-day skies are `GRAPHIC_ATMOSPHERIC_SKY_MIDDAY` (391) / morning / evening / midnight. No sky C3D in graphics.big — `CEngineSkyRenderer` builds the dome. | `DataCatalogTests` |
| Scene layers | Draws sorted `SceneLayer.Sky` (391 + 401), then Landscape, then Prop. Camera far is **4000** (helper +92); 7000 is SKY_DEF max flare radius 6000 — **not** the fog record and **not** first-seen WVP. `00B30B50` copies the unscaled view 3x4 to camera **+276** before cot-scaling +128 (`FirstSeenFogUsesUnscaledView=true`). First-seen fog **c2** is the **`00B47630`** linear view-Z plane (start **1000** / end **2000**, camera **+276**, view +Z = look): `dp4(world,c2)=(viewZ-start)/(end-start)`. `00B54310` inverse row 0 is mesh-path `00B555A0` only (`FirstSeenUploadsInverseRow0AsC2=false`, `FirstSeenFogC2IsLinearViewZ=true`). At SHOT2 that inverse row dots the house to ~35 and would force `oFog=0` (full black). First-seen VS (`VSHADER_LANDSCAPE_FOREGROUND` / `VSHADER_STATIC_DIRLIGHT_FOG` / `VSHADER_PALSKIN_DIRLIGHT_FOG`) do **`dp4 r.x, pos, c2` ; `mov r.w, c0.y` ; `min r.x, r.x, r.w` ; `mad oFog, r.x, -c18.w, r.w`**. That is **`oFog = c0.y - min(dot(pos,c2), c0.y) * c18.w`**, not `min(dot,1)*c18.w+1`. LayoutBasic first float4 **(0,1,2,0.5)** is the VS bank, not the PS `OUTPUT_FACTOR` slot. PS wrapper ctor **`0098ACF0`** writes **`(1,1,1,1)`** to PS bank slots 0–2; `PSHADER_TEXTURE_DIFFUSE` c0 is slot **2**. Landscape VS **c0.y=1** stays the LayoutBasic VS flush. **c18.w=1** at TOD 0 ⇒ `oFog = 1 - min(dot,1)`. D3D `oFog` saturates to **[0,1]** even though the `mad` has no `_sat` (`FirstSeenFogSaturates=true`). Nearby SHOT2 verts therefore stay **oFog=1**. Live client uploads `LinearFogPlane` as the mesh push `CameraPos` and the mesh VS clamps the same `mad`. D3DRS_FOGENABLE slot **`+10564`** (RS **28** at `+10576`). Ctor default **0**. Setter **`00B46890`** writes **1**. First-seen landscape setup `00B67480` and MainScene `00B32AD0` (layer bits **4** and **0x40**) both call it (`FirstSeenAppliesVertexFogBlend=true`). FOGCOLOR slot **`+10584`** (RS **34**): `00B47630` packs record (0,0,0,1)*255 as **`0xFF000000`**. Live clear is that black, not an invented sky blue. FOGTABLEMODE **35** / FOGVERTEXMODE **140** stay ctor **0** (NONE); VS `oFog` still blends when FOGENABLE is 1. Blend is `oFog*rgb + (1-oFog)*FogColor` = `rgb*oFog` at TOD 0. First-seen lighting is **`dp3 n, -c19`**; `max(., c0.x)`; square; `* c20`; `mad c35`. Live mesh PS does the same (`c19=(0,1,0,0)`, `c20=(0.25)×3`, `c35.rgb=0`). | `ShaderFormatTests`, `ScenePassTests`, `WorldSceneTests`, `CameraProjectionTests` |
| shaders.big | 26 banks, **465** programs. Payload = `u32` size + D3D tokens. **353** `vs_1_1`, **101** `ps_1_1`, **11** `ps_1_4`. `PIXEL_SHADERS` entries are type 1; vertex banks are type 0. | `ShaderFormatTests` |
| Landscape passes | `SHADERS_LANDSCAPE_FOREGROUND` (33 VS) + `PSHADER_LANDSCAPE_FOREGROUND` (**2** `tex` stages). Background is 1 tex. Proc-texture pass is 2 tex (`VSHADER_LANDSCAPE_PROC_TEXTURE`). Shadow / bump / colbuff variants add more stages. First-seen house/static compact ctor `00B8B630` looks up **`PSHADER_TEXTURE_DIFFUSE`** (1 tex, `mul v0*c0` then `mul_x2 t0`). `PSHADER_TEXTURE_DIFFUSE_FOG` is the same ops with `t0`/`r0` swapped — not the compact-ctor name. | `ShaderFormatTests` |
| Cell blend slots | Lookout 128² cells always write M0+M1+M2. M3 is `0xFF` on 16139/16384. `PSHADER_LANDSCAPE_FOREGROUND` is `tex t0; tex t1; mul_x2_sat r0.xyz, t1, v0; mul_sat r0.w, t0.a, v0.a` (dest shift 1 = ×2). `PSHADER_TEXTURE_DIFFUSE_FOG` is a plain `mul` (no ×2). Not a lerp. `INVALID_THEME_STANDIN` is skipped. `SEA_*` / `WATER_*` / `*LAKE*` are the water renderer, not landscape FG. Two real land layers bind as `TextureId`/`TextureId1`. | `ShaderFormatTests`, `WorldGeometryTests`, `LevFormatTests` |
| Landscape oPos − c4 | `VSHADER_LANDSCAPE_FOREGROUND` does `mov r0.xy, v0`; `mov r0.z, v1.x`; `mov r0.w, c0.y`; **`add r1, r0, -c4`**; `dp4 oPos, r1, c5–c8`. First-seen `c0.y=1`. Static / PALSKIN have **no c4** and transform `pos` directly. `00B54310` `push 4` writes inverse row 2 (`cam+236/+248/+260/+272`) to **c4**, but its only `E8` is mesh draw `00B555A0` (0 callers). Landscape `00B6B0B0` / per-cell `00BF4570` never call it and never `00989A60(4)`. First-seen landscape **c4 is D3D default 0** (`FirstSeenC4UsesDeviceDefault=true`). Subtracting inverse row 2 would invent a mesh-path constant. | `ShaderFormatTests`, `CameraProjectionTests`, exe `00B54310` / `00B6B0B0` |
| Landscape UV + bind | Tile verts are 15 bytes and have **no UV**. Draw `00B6B0B0` bit `0x40` walks patches `00BDC2D0` (4-plane frustum) then `00BF4570` per 16 m cell (`[patch+268/270] >> 4`). `SetTexture` (`[dev+260]`) stage 0 from cell+1468, stage 1 at `00BF5491`. `PSHADER_LANDSCAPE_FOREGROUND` is `mul_x2_sat r0.xyz, t1, v0` — **RGB is stage 1**. First-seen bind puts primary `TextureId` on t1 and `TextureId1` on t0. Strip material is sampled at the triangle centroid, not vertex A. VS wrapper `00989A60` is `SetVertexShaderConstantF` (`[dev+376]`, count 1). Register is **`[wrapper+4]+20 + slot`**. `0x1436E14` = static `0x13BC470`. `+4` = `&this+24`. Inner ctor `0098D4A0` zeros the 12-byte record at `+16` so **`[inner+20]=0`**. Per-cell `00BF4EB7` `mov edi, 2` is still edi at `00BF514F`: table `0x0139C5D8` → **c2**, table `0x0139C614` → **c3**. Fog flush restores **c2**; **c3** stays the table (`add r3,r3,c3`). Layer type 4 only writes **c1**. GPU expand `00BFE050` makes a **24-byte** VB. FG VS: **`oT0.xy = v3.yz`** is ExtraRgb.YZ (t0 alpha). Albedo is **`oT1 = dp4(pos,c40/c41)`**. No `def c40/c41` in the program. No layout field 40 (Basic/Lights/RepeatedMesh). No first-seen `SetVSConstantF(c40)`. D3D9 default for an unwritten VS constant is **0** (`FirstSeenOt1UsesDeviceDefault=true`, `Ot1C40=Ot1C41=(0,0,0,0)`). Emit is **`ProjectOt1` → (0,0)**. `UvScale=0.125` is the **c3 table**, not oT1. Layer types 3/4/5 enqueue on water. After the pass `00B67510` unbinds 0/1/2. | `LevFormatTests`, `ShaderFormatTests`, exe `00BF4EB7` / `00989A60` / `0098D4A0` / `00BFE050` |
| Uncovered 1 m cells | Oakvale adaptive strips omit many `GROUND_PATH_*` 1 m cells. Exe `00BF4570` submits stored tessellation only. Filling leftover 1 m cells from the WAD table was invented — **removed**. Village path cover is strip-only (less than every PATH cell). Water/decal still UNREAD. | `LevFormatTests` |
| Sea / water banks | Water vtbl `0x12A3364[+16]=00B783F0` (draw) / `[+4]=00B71FB0` (prepare) / `[+40]=00B7ED70` (query **1**). Prepare **`cmp eax,eax`** always collapses +508..+620 then **`00B6DC40`** bind. First-seen `+636=0` so bind returns 0 (`FirstSeenWaterPrepareFillsMesh=false`). Type-4 enqueue `00BF44A0` has one `E8` from per-cell `00BF57D1`: `[obj+28]==4` pushes onto water **`+0x244`** (a pointer list, not the 7363 mesh). Exe intern `__ENGINE_SEA_STATIC_MAP_BANK_FILE__` (`0x1436EC4`) / `__ENGINE_WATER_STATIC_MAP_BANK_FILE__` (`0x1436EC8`) / `__STATIC_MAP_COMMON_HEADER__` (`0x1436ECC`). STB stores 25 sea blobs named prefix+region (`StartOakVale` 129966, u32[0]=7363). No water-prefix STB entries. `00B6D4D0` stores the sea intern at **+1448** and the name string at **+1452**. The wanted-name dword at **+636** is assigned only by `00B23F00` (vtbl slot 14); first-seen never calls it. `00B41FA0` reads the water intern; type-8 dwords are discarded on the stack. First-seen sea bind `00B6DC40` does not run (`[+636]==0`), so `00B6D420` / `00BE91E0` never read the 7363 prefix (`FirstSeenReadsSeaPrefixWords=false`). When bind would run, word 0 is vertex count, word 1 index count, stride **12**, index format **101**. `SetStaticMapFileForUse` always calls `LoadWaterData` `00B41FA0` at `00B429CB` (intern `0x1436EC8`); first-seen miss is `00B420E4`. Draw `00B783F0` (vtbl+16) empty-out `00B7A865` is a bare `ret 4`. | `LevFormatTests`, exe `00B4282B` / `00B41FA0` / `00B6DC40` / `00B23F00` / `00BE91E0` |
| Tile extras | After XYZ: **11-11-10** packed normal + 3 bytes. Lookout: 289/289 unit Z-up; byte0 of the triple is always `0xFF`. `VSHADER_LANDSCAPE_FOREGROUND` tokens read `c20`/`c35` (and `c3`, which per-cell `00989A60` also fills with UV `±0.125`). Tile RGB is not oD0. Multiplying albedo by (1, ~0.5, ~0.5) made Lookout magenta. First-seen `c20=(0.25)×3+1`, `c35=(0,0,0,1)`. | `LevFormatTests`, `WorldGeometryTests`, `ShaderFormatTests` |
| Dirlight + fog | `PSHADER_LANDSCAPE_FOREGROUND` is `mul_x2_sat r0.xyz, t1, v0` with v0 = VS lighting (not tile RGB); alpha is `mul_sat t0.a * v0.a`. `PSHADER_LANDSCAPE_BACKGROUND` is `mul_x2_sat t0 * v0`. `PSHADER_TEXTURE_DIFFUSE_FOG` is a plain `mul` (no ×2). First-seen FG / static / PALSKIN all **`add oD0.xyz, lit, c3`**. Per-cell writes table `0x0139C614` = **`(0, 0.125, 0, 0)`** to **c3**; fog flush does not restore it. Unlit faces are that leftover, not black. Live mesh PS adds the same `c3`. Live bit 4 now does `mul_x2` on t0. Sand DXT mean is tan (102,93,74), grass olive (62,62,25). | `ShaderFormatTests`, `TextureFormatTests`, `CameraProjectionTests` |
| Sky | `stars.dat` 1330 points on a ~6500-unit sphere (xyz, unused 0, size, brightness). First-seen else-path **`00B662F0`** (not `0x400000`; we submit **`0x2000`**): sky camera 100/10000 / 0.99–1 then **`00B66190`**. **`VSHADER_INNER_SKY`** is **`dp4 oPos, v0, c5–c8`**. Bind **`00B62BA8`**: `[ecx+96]` `test ah,1` → **`PSHADER_INNER_SKY_SIMPLE`** (`00B62BB6`, 2 tex, `mul_x2 …, c1`) else **`PSHADER_INNER_SKY`** (`00B62C2D`, 4 tex, `lrp c0.w` / `lrp c1.w` / `mul_x2 …, c2`) into `this+292`. Neither program has **`def c0/c1/c2`**. `SetPixelShaderConstantF` is device vtbl **109** (`[dev+436]` / `0x1B4`); earlier `0x1A8/0x1AC` was the wrong slot. First-seen writer still unread — do not invent `*c2=0`. Both programs do `mul_sat r0.xyz, r0, v0` then `mul_sat r0, r0, v0.w` then `mul_x2_sat …, c2`/`c1`. `VSHADER_INNER_SKY` is `mov oD0, v1` plus `add oT0/1, v2, c92.xy` / `add oT2/3, v2, c92.zw`. Live consumes dome dest+12 alpha as `v0.w`. `*c2` still unread. PS upload is LayoutBasic flush `00989BF0` (`SetPixelShaderConstantF` start 0 via `[dev+436]`). Sky draw sites `00B631DF` / `00B634A7` / `00B63767` / `00B65937` / `00B66086` call it only when dirty bit **2** is set. First-seen bit and `[this+972]` bank values unread. Token listings: ExeIndex `out/01-sections/shader-tokens/`. Dome VB is **`00B620A0`** (stride **24**, FVF **`0x142`**, count **443**). Fill **`00B61DD0`**: **9 rings × 37 verts** (36 segs + wrap) from vert 73; `elev = ring * −0.1374447 + π/2`; **`xy = 6500 cos(elev) (sin az, cos az)`**, **`z = 3250 sin(elev)`** (origin 0). Cap **`00B61B30`**: (0,0,**7000**) + 36× cylinder at z=**−500**/**7000**, r=6500, step **2π/35**. Skirt **`00B61CD0`**: (0,0,**−10000**) + 36× (6500, z=−500) from vert 406. Colour dest+12 is **`(al<<24)\|0xFFFFFF`**: leftover **`vBase = 1 − 1.105 cos(elev)`**, clamp `max(vBase,0)`, `×255`, **`00BFEA70`** fistp. UV dest+16/+20 is **`xy * (1/[0x0143782C]) * (this+16 − sin(elev)) * this+20`**. CRT **`01224830`**: `fld qword [0x12A1140]; fcos; fmul 13000; fstp [0x0143782C]` (divisor ≈ 11583). Ctor **`00B627E2`** copies +16/+20/+12 from the current **`ENVIRONMENT`** object (`[0x1436E24]+292/+288/+296`, looked up at **`00B26828`**). Those slots are **CString** first-dwords: ctor **`0099AED0`** writes 0, persist **`004310A7`** / Transfer **`00430900`**. First-seen all three are **0** (`lightning_colours.tga` fills an earlier string slot). Dome UV is therefore **(0,0)**. Cap pole UV is **−0.0001, −0.0001**, colour 0; cylinder V=1, colour `0xFFFFFFFF`. First-seen star draw **`00B65A20`** (from **`00B66190`** at **`00B66284`**) hops `[0x1436E8C]+408` twice, then **`D=[C+84]`**. First dword at **`[D+424]==0`** → **`ret 4`**. That dword indexes a **44-byte** record, not `stars.dat`. Fade is **`1-*([D+436])`**. Sky ctor **`[this+396]=1`** so weather **`00B64FA0`** also runs: 4 ids from **`[D+472]`/`[D+448]`**, all-zero → **`00B659A5 ret 4`**, else bind/constants only (no new mesh). Invented Lookout `stars.dat` billboards are removed. | `ShaderFormatTests`, `CameraProjectionTests`, `WorldGeometryTests`, exe `00B61DD0` / `01224830` / `00B627E2` / `004310A7` / `00B65A20` |

## Did not work

| Guess | Why it failed | Locked by |
|---|---|---|
| WAD `.lev` is the landscape mesh | Parses as a 255-slot theme table. `MeshFile.TryParse` returns null. | `LevFormatTests`, dump `lev` |
| Framed LZO at WAD `.lev` payload is a dense f32 height grid | Almost no values in the TNG Z range. | `LevFormatTests.Lookout_payload_lzo_does_not_decode_as_dense_f32_grid` |
| `FindMeshId("OBJECT_WALL_SMALL_POST_01")` | No `MESH_WALL_SMALL_POST_01` in `meshdata.h`. Related names exist (`MESH_BS_WALL_SMALL_POST_02`) but that is the wrong object. | `MeshFormatTests` |
| `graphics.big` name contains `BRIGHTWOOD_MEDIUMROCK` | Zero hits. Rocks are not named after the TNG def. | probe 2026-03-16 |
| `game.bin` stores ASCII `OBJECT_*` / `#definition` | 996 KB, no those strings. Names live in names.bin. | `GameBinFormatTests` |
| `game.bin` is a table of `names.bin` hashes | Name-refs are **string offsets**, not CRCs. The wall hash never appears as a ref. | `GameBinFormatTests` |
| `game.bin` is Fable-framed LZO | Consumes ~8 KB, almost no ASCII. Chunks are **zlib**, not LZO. | `GameBinFormatTests` |
| Follow every OBJECT sub-def for a mesh | `MARKER_BASIC` / `CAMERA_POINT_*` have a `CAppearanceDef` editor mesh (4511/4512). Those are not world props. Only the object's own `Graphic` or a `CReplaceableMeshDef` is used. | `GameBinFormatTests` |
| WAD 21-byte `u16` at +8 is height | Every Lookout/Picnic cell stores **60**. It is a constant, not Z. | `LevFormatTests` |
| Material slot `u32` is a textures.big id | Lookout sand slot id 1911 decodes as `TEXTURE_BOWER_FEMALE_MIDDLE_LEGS_03C`. | `LevFormatTests` |
| STB section 2 is a packed xyz stream | `stride=12` from the section header does not yield a dense height point cloud. It is a raw-LZO tile, same as the table payloads. | `LevFormatTests` |
| Header `u16@+4` is the index count | It is D3D `PrimitiveCount` (`IndexCount - 2`). The two u16s after it are the last strip verts (valid, area ≈ 1), not the next object's attach pair. | `LevFormatTests` |
| Compressed tile bytes are a 17×17 f32 grid | Without LZO, almost no values sit in the TNG Z range. | `LevFormatTests.Compressed_tile_payload_is_not_a_17_by_17_float_grid` |
| 15-byte verts on the packed tile | Z floats appear at ~15-byte gaps, but XY is not packed local. The stream is LZO; verts are world `u16` after inflate. | probe 2026-08-16 |
| Every Lookout `DiffuseMapID` framed-LZO decodes | Some bank ids overrun the LZO reader. `TryLoad` returns null; those draws use a 1×1 fallback. | `GpuTextureTests` |
| STB `.lev` is the same format as WAD `.lev` | STB blob starts `u32=1`, not version 25 / `0x1904`. | `LevFormatTests` |
| Extra `Matrix4x4.Transpose` on VP | Double transpose. Screen was solid clear-color. | `CameraProjectionTests` |
| `CreateWorld(pos, RHSetForward, RHSetUp)` | Numerics CreateWorld is Y-up and **negates** forward. Lamp mesh Z (height) mapped to world Y — props lay on their side and faced backward. | `WorldGeometryTests` |
| TNG positions are WLD (MapX+local) | Lookout things sit in 29–128, not 3232+. Adding MapX would throw them off the terrain. | `WorldGeometryTests` |
| Ignore `ObjectScale` | Rocks/pillars store 0.4–1.2. Without it they instance at 100% mesh size. | `WorldGeometryTests` |
| Draw STB tiles as a dense 1-unit grid | Adaptive tiles have ~159–280 verts, not 289. Bilinear-filling the rest raised hills through objects (a Lookout rock sat 3.3 m under stamped Z). | `LevFormatTests` |
| Fill uncovered 1 m PATH/WATER cells from the WAD table | Exe landscape draw submits stored STB strips / 17×17 only (`00BF4570`). Extra 1 m quads are not that pass. | `LevFormatTests`, exe `00BF4570` |
| House C3D 6909 emits roof 3182 and floors 3184 | File `primitiveCount` is **2**. Those materials exist but have no primitives. Emitted ids are 345 and 3180. Interior is multi-static **6911** (3172 walls), not invented 3184 quads. Floor under SHOT2 is landscape **4130**. | `MeshFormatTests.Heros_old_house_c3d_emits_walls_and_floors`, `MeshFormatTests.Heros_old_house_interior_is_multistatic_6911_not_floor_3184` |
| New Game 276 missing meshes are house/prop C3D | Client `missing=276` is 155 Marker + 102 Thing + 19 TrackNode: `MARKER_BASIC` 67, `PARTICLE_EMITTER_PLACEABLE` 47, `CAMERA_POINT_*` 55, gaze/crate/light markers (`GAZE_*` is TypeName `MARKER`, not a `MARKER_` prefix), `TRACK_NODE_BASIC` 19. Zero `noEntry` / `noBank` / `parseFail` among Graphic defs. First-seen C3D is Graphic / CMultiStatic / CReplaceable only (`FirstSeenInstancesAsC3d`). Particle create `006E0880` looks up `PARTICLE_EMITTER_NORMAL`, not a C3D. Track type name is `THING_TYPE_TRACK_NODE` `004C76A5`. Live no longer counts gizmos as `MissingMeshes`. | `WorldGeometryTests.New_game_oakvale_loads_contains_and_sees_maps`, exe `006E0880` / `004C76A5` |
| First-seen house-area TNG objects lack Graphic | StartOakValeWest OBJECT/BUILDING/CREATURE/GENERIC within 25 m of HerosOldHouse all resolve Graphic / CMultiStatic / CReplaceable bank ids (`FirstSeenHouseAreaDefsResolveGraphic`). Kid **4300** is `MESH_YOUNGHERO_02` face/torso/mouth/hair (no extra CMultiStatic). Apply vtbl persist slots are `ret` stubs; file CRC immediates are not in `007E*`. File→runtime persist is **`004EB8C3`**: FlagA/FlagB/Value/Tail → **+44/+45/+48/+52**. First-seen skip-global `[0x13756F0]=-1` so skip stays off. | `WorldGeometryTests.New_game_oakvale_loads_contains_and_sees_maps`, exe `0x126FFB4` / `004EB8C3` |
| First-seen house interior is C3D 6909 / invent 3184 floor quads | BUILDING Graphic is **6909** (ext walls 345). Subdef **`CMultiStaticMeshDef`** `Meshes` count **2** is **6911** then **6909** (34-byte entries, `Mesh` CRC `0x60194A74`). 6911 is **walls (int) 3172** (898 tris). **3184 has 0 prims on both C3Ds**. Floor under SHOT2 is landscape **`LANDSCAPE_PATH_STONEY_01` (4130)** at house Z plus the rug TNG. Registrar `00522A20` binds `"BUILDING"` to factory **`0052AC10`** → `005296B0(3)` → `004C9030`. `007E1400` looks up the multi-static def. Apply is vtbl `0x126FFB4[0]` **`007E15C0`**: 56-byte runtime records (`imul 0x92492493` /56), mesh id at **+40**. Skip is `[0x13756F0]>=0 && selected!=0` (`selected` = `+52` when `+44!=0`, else `[thing+64]`). File 34-byte entries are Mesh CRC + id + four tagged fields (`0x7CA90715` u8, `0x97595FC1` u8, `0x15DC93E9` f32, `0x2DF5F1FA` u32): 6911 is **0/1/40/0**, 6909 is **1/0/0/0**. Persist **`004EB8C3`** (`0x12438A4[+72]`) writes those via **`00431102`** dword (`+40` mesh), **`0043314A`** u8 (`+44` FlagA, `+45` FlagB), **`00431061`** float (`+48` Value), **`004735D6`** dword (`+52` Tail). Vector persist is **`004EDE1B`** `add ecx,40` → **`004EDE2B`** (count then each `[vtbl+72]`). Skip `007E1716` reads **`[eax+44]`** then **`mov al,[eax+52]`**. First-seen **`[0x13756F0]`** is the `.data` dword **`-1`** (only readers `007E1788` / `0077BAC5`; `FirstSeenSkipGlobalHasWriter=false`), so skip is off even when FlagA=1. `004C9030` zeros `CThing+32` through `+64` (`FirstSeenThingPlus64IsZero=true`). `CTCBuyableHouse` ctor `006BF8A0` / construct `006C14D0` looks up the def and window names, but `0082E0E0` needs `[thing+56]&0x200000`; first-seen that bit is 0 so `006BFB90` returns 0 and **does not** call window-swap `006C0F00` (`FirstSeenBuyableHouseSwapsWindows=false`). `CBuyableHouseDef` `Price` is four gold tiers (5000/7500/11000/16000), not a mesh. First-seen skip does **not** drop either house mesh (`FirstSeenMultiStaticAppliesBothHouseMeshes=true`). Script `CREATEBUILDING` `0072DF50` is not the TNG first-seen spawn. | `GameBinFormatTests`, `MeshFormatTests.Heros_old_house_interior_is_multistatic_6911_not_floor_3184`, `WorldGeometryTests`, exe `0052AC10` / `007E1400` / `007E15C0` / `006C14D0` / `0082E0E0` / `00522A20` |
| Static/PALSKIN albedo from `t1` / invented UV | First-seen static VS is **`mov oT0, v2`**. `00BB2540` VB is FVF **`0x112`** (`XYZ\|NORMAL\|TEX1`) stride **32**. PALSKIN is **`mov oT0, v4`**. Compact ctor **`00B8B630`** (engine init `00B26662`) looks up **`PSHADER_TEXTURE_DIFFUSE`**: `tex t0`; `mul r0, v0, c0`; `mul r0.w, t0, r0`; `mul_x2 r0.xyz, t0, r0`. Caller **`00BB30A0`** at `00BB3D91` pushes texture-count **1**; bind **`00BB301E`** is `SetTexture(stage, [array])`. VS bind is **`00988020`**; PS attach is **`00988140`** (`[0x1436E78]+0x1AC`). Other `00BB2540` `SetTexture` sites are unbind loops (`stage, 0`). First-seen PS **c0** is **`PSCONST_OUTPUT_FACTOR`**: ctor **`0098ACF0`** registers that name as bank slot **2** (`0098DB20` third insert) and writes **`(1,1,1,1)`** at `[bank+32]`. Flush **`00989BF0`** copies `bank[2]` to dest **c0**. First-seen `[wrapper+913]=0` so the material overwrite of slot 2 does not run (`FirstSeenStaticPsC0HasWriter=true`). Live **`2*t0*v0`** is that identity. PALSKIN default attach is **`[0x1436E78]+0xDC`** then the same flush. | `ShaderFormatTests.First_seen_static_ps_is_texture_diffuse_mul_v0_c0_mulx2_t0`, exe `00B8B630` / `00BB30A0` / `00BB2540` |
| `00CB7940` skins or poses the kid | Six instructions: `[ecx+44]==0` → false, else return `[eax+5]`. Hero-already-spawned check used in the StartOakVale wait loops. | exe `newgame-trace` v9 `hero-exists-00cb7940` |
| `004AA840` constructs `CREATURE_HERO_CHILD` | Releases `[this+8]`, clears +4/+8, `jmp 0099A2E0` (writes vtbl `0x1231710`). Called on a stack temp after the creature lookup. | exe `newgame-trace` v9 `string-dtor-004aa840` |
| `scanff 228` finds `SetRenderState` | `FF 50 E4` is `call [reg-28]`. Real `IDirect3DDevice9::SetRenderState` is `FF 9x E4 00 00 00` (`calldisp 0xE4`). | exe `calldisp 0xE4` → `00A044E0` |
| Wrapper `[this+4]+20` is an unread register bias | `0x1436E14` = static `0x13BC470`. `+4` = `&this+24`. Inner `+20` is zeroed in `0098D4A0`. Slot **is** the D3D `c` index. Landscape `push 3` writes UV `±0.125` to **c3**, not lighting. | exe `00989A60` / `0098D4A0` / `00B26500` |
| Emit `SEA_*` / `WATER_*` as landscape `442` | `LoadWaterData` `00B41FA0` rejects unless first u32 is **8**. StartOakVale sea is **7363**. Draw `00B783F0` jumps to `00B7A865` when +508/+512 and the other water vectors are empty. First-seen has no type-8 water bank, so those cells are not landscape FG. | `LevFormatTests`, `WorldGeometryTests`, exe `00B41FA0` / `00B783F0` |
| Invented N·L `0.28 + 0.72 * n·sun` with guessed sun `(0.35,0.25,0.90)` | LayoutLights `+84=19`, apply `00F39D40` + ctor: **c19=(0,1,0,0)**, **c20=(0.25)×3+1**, **c35=(0,0,0,1)**. Not a 0.28/0.72 scale. | `ShaderFormatTests.First_seen_vs_read_c20_and_c35`, exe `00BDB400` / `00F39D40` / `00B482A0` |
| `00A3A7F0` / `00A3A85E` `mov [esi+84], 1` is the SHOT2 two-FOV flag | Pushes `"1.2.1"` into `00C021C0` / `00C02310`. +84/+85 are a stream-version ready flag on that object, not camera source+84. Camera +84 is still the `00B30B50` byte that selects two FOV radians vs 4:3 letterbox. | exe `00A3A7F0` / `00A3A840` |
| `[+18068]==1` picks the 2LIGHTS VS slot | Ctor writes 1. Setter `00B23C00` is a 1-or-2 **mode** (arg0→1). Compact-bind / static draw only copy that into dirty byte `[+206]`. The VS slot is `min(packed count, 5)` then family remap, not this mode. | exe `00B23C00` / `00B68EE6` / `00BA2677` / `00BB5040` |
| Remap `[i]=i` so packed count 1 binds 2LIGHTS | Family+32..+52 are ctor zeros. MainScene `00B34619` does not refill them. `SelectFamilySlot` is **0**. First-seen packed count is **0** (add-light message 16 is not sent from `0089FAA8`). | `ShaderFormatTests.First_seen_vs_read_c20_and_c35`, exe `00BB5040` / `00B34619` / `00B481E0` |
| First-seen VS `LIT` `c35` | `VSHADER_LANDSCAPE_FOREGROUND` / `VSHADER_STATIC_DIRLIGHT_FOG` / `VSHADER_PALSKIN_DIRLIGHT_FOG` do `DP3` `c19`, `MUL` `c20`, then **`MAD` `c35`**. Opcode `0x10` `LIT` is not in those programs. | `ShaderFormatTests.First_seen_vs_read_c20_and_c35`, exe `0098A760` / `0098B2C0` / `00B46F23` |
| PALSKIN `c1=1/255` or absolute `c38` | LayoutBasic second float4 is **`c1=(256)×4`**. VS is `v1.zyxw*c1` then **`a0`-relative `c38`**. | `ShaderFormatTests.First_seen_vs_read_c20_and_c35`, `MeshFormatTests`, exe `00BDBB70` / `00989BF0` / `00BD549D` |
| First-seen static/PALSKIN `oD0.w` from `c42` | Tokens are **`mov oD0.w, c0.y`**. Only FG reads c42. No first-seen c42 writer, so FG `oD0.w=0`. | `ShaderFormatTests.First_seen_vs_read_c20_and_c35`, exe `VSHADER_*` |
| Invented FogColor `(0.52, 0.58, 0.68)` / start 0 / end 7000 as `c18` | `c18` is record `+64..+76` = **(0,0,0,1)** at TOD 0. Fog start/end are **1000/2000** (`0x447A0000` / `0x44FA0000`). 7000 is SKY_DEF far, not the record. Live clear is black. | `ShaderFormatTests`, `CameraProjectionTests`, exe `00B47630` / `00B4844F` |
| First-seen fog `c2` = camera inverse row 0 | `00B54310` `push 2` writes inverse row 0, but only from mesh `00B555A0`. First-seen `00B47630` writes the linear view-Z plane (1000/2000) and fog flush restores `c2`. At SHOT2, `world · InverseRow0` is ~34–40 so `oFog=0` and the house goes black. | `CameraProjectionTests.First_seen_fog_c2_is_linear_view_z_not_inverse_row0`, exe `00B47630` / `00B54310` |
| Invented GLSL `max(n·+Y, 0)` / `nlen<0.1 → 1` lighting | First-seen VS is `dp3 n, -c19`; `max(., c0.x)`; square; `* c20`; `mad c35`; **`add c3`**. `c19=(0,1,0,0)` so floors (`n=+Z`) get **c3=(0,0.125,0)**, not a fill-in 1. | `CameraProjectionTests.First_seen_fog_c2_is_linear_view_z_not_inverse_row0`, shaders.big |
| `CreatePerspectiveFieldOfView(72, aspect, 0.15, 7000)` as first-seen WVP | `009883F0` / `00B3106C` use helper **0.1 / 4000** and viewport **0.1–0.99**. FOV is letterbox-scaled horizontal (`00B30B50` +84 clear), not Numerics vertical. | `CameraProjectionTests.First_seen_wvp_uses_helper_near_far_and_viewport_z`, exe `00988A50` / `009883F0` / `00B314E0` |
| First-seen view = `CreateLookAt(right, up, -forward)` | `00B314E0` normalizes helper look/up, `right = up × look`, packs **`(right, look, up)`** at +16. Look is not Gram-Schmidt'd. VS `dp4 oPos.w` is `clip.w=view.z`, so the Numerics 4x4 is **`(right, up, look)`** (`FirstSeenViewLookIsZ`). Putting look on Y (raw `00988350` rows without the upload-transpose) sends SHOT2 look-at to NDC Y≈−8 and blacks the house. `CreateLookAt` Z is `-fwd`. | `CameraProjectionTests.First_seen_wvp_uses_helper_near_far_and_viewport_z`, exe `00B314E0` / `009883F0` |
| First-seen landscape `oPos` subtracts inverse row 2 | Tokens do `add r1, r0, -c4`. `c4` inverse-row-2 writer is `00B54310`, only from mesh `00B555A0`. Landscape first-seen never writes c4, so D3D default **0**. | `ShaderFormatTests.First_seen_vs_read_c20_and_c35`, `CameraProjectionTests.First_seen_landscape_opos_subtracts_default_zero_c4_not_inverse_row2`, exe `00B54310` / `00B6B0B0` |
| Put landscape `T(cam)` into the shared FlyCamera VP | `00BF46A2` is per-cell landscape only. `00B2FC50` writes identity world for sky/bind. House props use instance world. A global `T(cam)` would move the interior off SHOT2. | `CameraProjectionTests.First_seen_view_is_camera_plus128_and_landscape_world_is_camera_translation`, exe `00BF46A2` / `00B2FC50` |
| First-seen sky uses the world 0.1/4000 WVP | Else-path `00B662F0` writes near **100** / far **10000** / minZ **0.99** / maxZ **1** then `00B66190`. `VSHADER_INNER_SKY` is `dp4 v0, c5–c8`. | `ShaderFormatTests.First_seen_inner_sky_opos_is_dp4_v0_c5`, `CameraProjectionTests.First_seen_sky_wvp_uses_100_10000_and_far_slice`, exe `00B662F0` |
| Invented 12×32 sphere r=1800 at Lookout `(64,64,0)` | `00B61DD0` is **9×36** ellipsoid **6500×3250** at origin. Cap/skirt use r=6500 at z=7000 / −500 / −10000. | `WorldGeometryTests.First_seen_sky_dome_is_6500_by_3250_ellipsoid`, exe `00B61DD0` / `00B61B30` / `00B61CD0` |
| Invented sky-dome UV `(seg/36, ring/8)` | Fill writes `U,V = xy * (1/[0x0143782C]) * (this+16 − sin(elev)) * this+20`. Divisor is CRT **`13000 * cos(last-ring elev)`**. `this+16/+20` are empty CString first-dwords (**0**). First-seen UV is **(0,0)**. | `WorldGeometryTests.First_seen_sky_dome_colour_is_white_with_vbase_alpha`, exe `01224830` / `004310A7` / `00B627E2` |
| Invented object PS `t0 * v0` without `_x2` | Compact ctor binds **`PSHADER_TEXTURE_DIFFUSE`**: `mul r0, v0, c0` then **`mul_x2 t0`**. Plain `t0*v0` is half as bright as the tokens. First-seen `c0` is identity `PSCONST_OUTPUT_FACTOR`. | `ShaderFormatTests.First_seen_static_ps_is_texture_diffuse_mul_v0_c0_mulx2_t0`, exe `00B8B630` |
| Invented Lookout-centered `stars.dat` billboards at `(64,64,0)*1700` | `00B65A20` gates on first dword at `[D+424]==0` then `ret 4`, else looks up a **44-byte** record and binds the already-bound sky stream. It does not walk `stars.dat` (24-byte / 1330). Map-manager `+424` is OpenStaticMaps mode, not the star list. | `WorldGeometryTests.First_seen_star_draw_does_not_emit_stars_dat_billboards`, exe `00B65A20` / `00B66190` / `00B42750` |
| `00DB8680` starts `CS_OAKVALE_INTRO_FATHER` | Xref `00DB88DE` is inside the **next** function. `00DB8680` is the microthread **dtor** (vtbl `0x012D95B0[0]`; update still `00A44880`). Start is **`00DB86B0`** → `00CBFB7D`. | exe `script-runtime` v9 |
| `0x012D838C` is a C++ vtbl that first-seen indexes | That dword is the **run** slot after the `NOVI_LiveFather` string. Name-record vtbl is **`0x012D8370`**. LiveFather object vtbl is **`0x012D8388`**. Zero `E8` of `00DB86B0`; start is construct-time `00CB8960` → `00DAC2C0`. | exe `script-runtime` v10 |
| Emit ExtraRgb.YZ as the landscape albedo UV | FG VS `oT0.xy = v3.yz` is ExtraRgb (~0.5) for **t0 alpha**. Albedo is **oT1** from `c40`/`c41`. Oakvale extra Y/Z do not track world XY × 0.125. Live now stores ExtraRgb and samples t0 at YZ (FG) / XY (BG). | `LevFormatTests`, `ShaderFormatTests`, exe `00BFE050` / `VSHADER_LANDSCAPE_FOREGROUND` / `VSHADER_LANDSCAPE_BACKGROUND` |
| Landscape bit-4 is a plain `t0*v0` | `PSHADER_LANDSCAPE_BACKGROUND` is `mul_x2_sat t0 * v0`. | `ShaderFormatTests`, exe `PSHADER_LANDSCAPE_BACKGROUND` |
| Emit world XY × 0.125 as landscape albedo UV | Per-cell writes that table to **c2/c3**, not c40/c41. First-seen c40/c41 have no writer and no `def` — D3D default **0**, so `oT1=(0,0)`. | `LevFormatTests`, `ShaderFormatTests`, exe `00BF4EB7` / `00989A60` / `0098D4A0` |
| VS `oFog = min(dot(pos,c2),1)*c18.w+1` | Tokens are **`mad oFog, min(dp4(pos,c2), c0.y), -c18.w, c0.y`**. The scale is **negated** `c18.w`. First-seen `c0.y=1`, `c18.w=1` ⇒ `1-min(dot,1)`. | `ShaderFormatTests.First_seen_vs_read_c20_and_c35`, shaders.big |
| Tile leftover is a triangle *list* | Bytes `0,0,1,2,2,0,0,3…` are a **strip** with degenerate restarts. Reading groups of 3 dropped every other half-quad (flat ground looked like isolated triangles). | `LevFormatTests` |
| Format 35 is DXT1 | First LZO frame is 262144 bytes (16-byte blocks). DXT1 top mip is 131072. | `TextureFormatTests` |
| First-seen files store only the top mip | 414 / 4130 / 3172 leftover after the top framed LZO is **43688** on all three = raw DXT1 256..4. `PayloadIsTopMipOnly` is false. | `TextureFormatTests` |
| LZO expected = full mip-chain size | Framed tail is the last 3 bytes of the *requested* size. Expected=chain leaves `produced=top−3` and treats leftover as more LZO (produces 0). Expected=`TopMipSize` yields 131072 and leaves the raw lower mips. | `TextureFormatTests` |
| `00416D20` is DXT5 CreateTexture / graphic-bank open | Mid-function after `call 009BE8B0`. `push DXT5` + `009E3830` is a CString. Function start is `00416C8A` (`Init Graphics`); bank-open strings are later in the same fn. | exe `00416C8A` / `00416D20` |
| `005B37F7` DEFAULT is first-seen kid pose | Only `E8` callers are clothing GUI `005B6881` (`TEXT_GUI_MENU_CLOTHING_TOTAL_ARMOUR`) and `PC_UI_FRAME` `005B8743`. Create `006AC910` does not call it. The fn does reach inner play `0070D580`; first-seen does not. | `MeshFormatTests`, exe `005B37F7` / `005B6881` / `005B8743` |
| Format 35 is DXT3 | First 8 bytes `FFFF000000000000` are DXT5 `a0=a1=255`. As DXT3, 12/16 texels get alpha 0. Combined with a `discard` the sky became horizontal stripes. Exe `PSHADER_INNER_SKY` has no `texkill`. | `TextureFormatTests` |
| WAD `Find("Lookout")` for a `.tng` | Stem match hit `LookoutPoint.lev`. Must pass the extension. | `TlcInstallTests` |
| `FinalAlbion.gtg` is a compiled .lev / C3D | ASCII `NEWMAP 1` / `Version 2;`. First `u32` is not 25. | `DataCatalogTests` |
| PicnicArea.lug / Dialogue.lut are BIGB | Magic is `LiOnHeAd`, not `BIGB`. | `DataCatalogTests` |
| BWD declared count is the parsed region count | File says 399; 398 records match WLD. Trailing ~22 KB is not another region. | `DataCatalogTests` |
| WLD `MapUIDCount 72` is the number of maps | The file then has **398** `NewMap` blocks. 72 is some other counter. | `WorldSceneTests` |
| `EntranceConnectedToUID` is a raw thing UID | The u64 is not an instance id. High bits are MapUID; low 32 bits equal the dest entrance's low 32 bits (`0xFFFFFE00…`). | `WorldSceneTests` |
| Lookout STB 64/64 means the overworld is complete | Every Lookout tile is present (~30039 tris, 192 props). The missing ground west/east/north/south is **other WLD maps**. Teleport-graph neighbours are not those tiles. | `WorldGeometryTests`, exe `OpenStaticMaps` |
| Starting-region graph is the visual neighbourhood | Lookout graph lists Picnic, BowerstoneSlums, GreatwoodEntrance, guild interior, demon door. Only Picnic shares an AABB edge. Slums / GreatwoodEntrance / interiors do not touch Lookout's rectangle. | `WorldSceneTests` |

## Open

0. **PlayAVI timing vs Steam Fable.exe.** Live Receive is **`00CA7210` → `00CA6C40` PrepareReceive → `00CA6E10` → `00CA65B0` WaitForRenderTime → `00CA4B20`/`00A3B730` → `00CA4BF0` ClearPending**. `PlayAviFromExe.cs` is ExeIndex evidence. Do not clone whole CBaseRenderer. Lionhead overrides: C++ `00A3BCD0` is `xor eax,eax; ret` (not the quartz call), `00A3BCF0` DoRenderSample ret, `00A3B730` copy, RGB24, WaitEx 33. Dump family `trace-playavi-timeline` v10. **Quartz QIs IBaseFilter (`00CA7500` returns C++ +12, vtbl `0x0129D04C`) and calls slot 8 `00CA7680`**, which AddRefs the clock and stores it at COM this+12 = **filter+24**. Ctor `00CAC830` zeros +24; `00A3B510` writes `[+12]=0x0129D04C`. **`00CA49F0` GetSampleTimes** reads C++ `[this+24]`; clock set → ShouldDraw `00CA5850` (subtract `0x13880`) → S_FALSE → **`00CA4AA0` AdviseTime** `[clock.vtbl+16]` at `00CA4B07` with tStart from **`00CA68F0` → `00CAACA0`** (COM this+20/+24 = filter+32/+36) and +84 handle. **`00CA65B0`** `WaitForMultipleObjects(+88,+84)` 10 s retry until the clock signals +84 (`00CB0190` CreateEvent push 0 = auto-reset). That delay is the sample interval, not a C# 33 ms sleep (`FirstSeenPlayAviReceiveWaitsOnSampleTime=true`). Then **`00CA4B20`/`00A3B730`** one texture copy + SetEvent(player+124). Pin **`00CA89F0` GetAllocator** CoCreates **`CLSID_MemoryAllocator`** (`0x12AAC24` = `1e651cc0-b199-11d0-8212-00c04fc32c45`) + **`IID_IMemAllocator`** (`0x12A9A54` = `56a8689c-0ad4-11ce-b03a-0020af0ba770`); **`00CA8EC0` reqs are E_NOTIMPL** so the decoder sets `cBuffers`. One sample at +108 is the queue. `006286F0` WaitEx(33) is only the Present wait. Not a C# ring. FIFO during the pump. Do not launch Steam `Fable.exe`.
0b. **First-scene runtime leftovers.** The 24-byte list walk is `00CB6EA0`; the exact `call [vtbl+8]` site that resumes a yielded `00DABAC0` is still UNREAD. Who writes `[quest+80]=1` after the blocking `vtbl+2584(12)` and `HerosOldHouse` lookup is UNREAD (not a `mov` in `00DBDE00–00DBF000`; later-quest `+80` stores are not this gate). `vtbl+28` yield body on `[0x143E8F8]` is UNREAD as a concrete fn. `Main` watcher callback `00CDD440` is UNREAD. `00DB86B0` is reached from first-seen `NOVI_LiveFather` thing construct after `00DABAC0` registers the name (`FirstSeenStartsIntroCutscene=true`). `00CBFB7D` FadeOut 0.5,0 **special-case does not run** (`FirstSeenFadeSpecialCaseRuns=false`; first line is PlayMusic). PlayMusic (`00CC8EAC` / `00CBF7FE`) looks up via `009E5120` and `vtbl+2784`, then **`jmp 00CD17FD`** (no yield). `00CD17FD` increments the line index and **`jb 00CC012E`**. Next line is `FadeOut 0.5,0` (`00CD0987`): default 0.5 / 0 / black `(0,0,0,255)`, then **`vtbl+1488(0.5, 0)`** and `jmp 00CD17FD`. `FirstSeenFadeOpcodeInStartOakVale=true`. PlayAVI / wake stay later (`FirstSeenPlayAvi=false`). Do not invent AVI/wake or a guessed fade quad; apply slot `vtbl+1488` body is still UNREAD. Kid PlayAnimation after create is still unused (`FirstSeenPlaysAnim=false`); per-frame bone-palette advance therefore stays identity. Player-control handoff after PostAttack is UNREAD (`FirstSeenHandsPlayerControl=false`). Do not invent a fade, a standing anim, or a 12 s `+80` writer. `CS_OAKVALE_INTRO_FATHER` starts from generic thing activate of `NOVI_LiveFather`, not from `NewGameScript`. FlyCamera is debug-only; scripted camera is TNG `CAM_OVIF_SHOT2`. First interpreter slice now includes proven `.Teleport` (no yield), `.LookToThing` (execute then `vtbl+28`), `DoScriptFrame N` (one `vtbl+28` per count; first-seen N=1), and `DoCameraPreloading` (no yield; `00CBF29F` name preload only — `vtbl+1564/+1568` bodies UNREAD). Next unread command after `MuteSounds false` + second `FadeOut 0.5,0` is **`DoScriptFrame 2`** (already proven: two `vtbl+28`). Then two more `00A44880` resumes finish `DoScriptFrame 2` and **`Hero.PlayAnimation CS_WAKING_UP_LOOP,FALSE,FALSE,TRUE,FALSE`** (`00CC14B8`: thing `vtbl+72` name + flags `(0,0,1,0,0)` + `[0x1375748]` first byte 1 + 0; `[ebp-22]` ctor `00CBFD57=1` → `00CC186F` → `00CC5691` one `vtbl+28` then `00CC0EBC` → `00CC7081`). Next line is `CS_WAKING_UP_ON_STEPS` (same yield-once). `vtbl+72` body UNREAD — record name+flags only, do not invent a wake pose. Then four more resumes finish **`DoScriptFrame 4`**. **`StartTimeCode`** (`00CD1373`) zeros `[0x13B83C8]` and **`jmp 00CD17FD`** (no yield). Same slice then `PlayMusic MUSIC_SET_OAKVALE`, `NoLoadUseCamera CAM_OVI_ID_STANDUP` (already Continue), `FadeIn`, and **`GamePause 1.6`** (`00CC88D1` default path: `0099E690` atof * `[0x124E640]=15` → 24, one `vtbl+28`, then loop `vtbl+28` adding `[0x122DED8]=1` until counter ≥ 24). That is **not** 1.6 s of `dt` (`FirstSeenGamePauseUsesFrameDt=false`). CLOCK arg is unread (first-seen has none). Then **`Father.Speak Father,'TEXT_QST_048_FATHER_INTRO_10'`** (`00CC25FD`: apply thing `vtbl+52`, poll `vtbl+104` with `vtbl+28` until `al==0`). Father vtbl `0x0127293C` +52 is `004CD1B0` `al=1`, +104 is `00661A40` `ret 4` (leaves al) so first-seen is **one yield** (`FirstSeenSpeakYieldsOnce=true`). Do not invent dialogue UI. Then **`GamePause 1.0`** (same 15× wait) and **`Father.InteractiveSpeak Hero,'TEXT_QST_048_FATHER_INTRO_20',FALSE,'TEXT_QST_048_FATHER_INTRO_30'`** (`00CC2EAA`: context `vtbl+1456/1460/1464`, third arg `00CBEDBA` FALSE → one `vtbl+28` then `00CC707C`). TRUE would poll unread `vtbl+1472`. Do not invent dialogue UI. Then **`GamePause 1.2`** and **`UseCamera CAM_OVIF_SHOT2`** (`00CC9F39`: one-arg name bind `vtbl+1648`, `[ebp-37]` ctor `00CBFD53=1` one `vtbl+28` then `00CD17FD`). `NoLoadUseCamera` is the same yield (`00CC9E69` → `00CC907D`). Classify-as-Continue is DISPROVEN. **`GamePause 5.2`** is the same 15× wait (78 increments). Then **`Hero.PlayAnimation CS_TIRED`** records name+flags and yields once. Thing `vtbl+72` is **`004C7470`**: walk `[this+68]→[this+72]` 8-byte records and call `[comp.vtbl+68](name)` (`ret 4`). CTCAnimationComplex factory **`0070B3F0`** writes vtbl **`0x012650A4`**, type id **90**, inner **`0xBC`** at +12. Complex **`+68`** is **`00686920`** `mov al,1; ret 4` (not handled). Real play **`0070D580`** (request **`0070C050`**, getter **`0070B460`**) is **not** on the `00CC15DA` path (`FirstSeenPlayAnimationAppliesPose=false`, `FirstSeenPlayAnimationCallsInnerPlay=false`). `005B37F7` plays **DEFAULT** via `0070C050`/`0070D580` on clothing GUI / `PC_UI_FRAME` only (`FirstSeenAppearancePlaysDefault=false`); it is not first-seen kid attach — not script PlayAnimation. Do not invent a wake/tired palette. **`GamePause 2.0`** is the same 15× wait. Then **`Father.DialogSpeak HERO,'TEXT_QST_048_FATHER_INTRO_60'`** (`00CC3165`: empty/null skip `00CC7081`; context `vtbl+1456/1460/1464` = `008906C0`/`00890710`/`00890750`; leftover-session poll `vtbl+1472` `008907D0` → `006E5660` UNREAD as UI; one `vtbl+28` then `jmp 00CC707C`). `FirstSeenDialogSpeakYieldsOnce=true`. Do not invent dialogue UI. Then **`Hero.WaitTask FOO`** (`00CC0783`: arg unused; no actor → `00CC7081`; else poll thing `vtbl+104`. Hero vtbl `0x012457FC` +104 is `006A9550` `jmp 00661A40` `ret 4`. First leftover after token dtor is busy so one `vtbl+28`; `00CBF7FE` empty path leaves al=0 and the next poll continues. Fiber `[0x13D2838]+5` is 0 while running). `FirstSeenWaitTaskYieldsOnce=true`, `FirstSeenWaitTaskReadsName=false`. Do not invent a task table. Then **`Hero.SneakTo MK_OVIF_HERO4,0.0,FALSE,FALSE,FALSE`** (`00CC0CB5`: empty actor/marker skip; speed default `0x3E99999A`=0.3, first-seen 0.0; arg2/arg3 IsTrue would poll `vtbl+104`; first-seen FALSE takes one `vtbl+28` then `00CC7081`. Thing `vtbl+20` is `004C72B0` `al=1; ret 4` — no mesh move. Mode push 2). `FirstSeenSneakToAppliesMove=false`, `FirstSeenSneakToWaitsForArrival=false`. **`GamePause 1.0`** is the same 15× wait. Then **`Father.PlayCombatAnimation TURNING_AC90,FALSE,TRUE`** (`00CC15E3` `.PlayCombatAnim`: empty actor/name skip; arg1 IsTrue discarded; arg2 TRUE sets a flag; thing `vtbl+76` Father `00834760` / player `006AD9D0` do **not** read the name; `[ebp-22]` one `vtbl+28`). `FirstSeenPlayCombatAnimationAppliesPose=false`. Do not invent TURNING_AC90 bones. **`GamePause 1.0`** then **`Hero.PlayAnimation CS_LOOK_LEFT,TRUE`** (already proven PlayAnimation). Then **`Create CREATURE_OAKVALE_VILLAGER_FEMALE_NORMAL_MESH,MK_OVI_ID_VS1,VILL1`** (`00CCC246`: type/marker/name required else `00CD17FD`; apply `vtbl+364` `008A9100`; **`jmp 00CD17F8`** no interpreter yield). Spawn body UNREAD — record only, do not invent a villager mesh. Then **`VILL1.WalkTo MK_OVI_ID_VW1`** (`00CC083D`: same parse as SneakTo; first-seen marker only so arg2/arg3 IsTrue is false → `jmp 00CC0E96` one `vtbl+28` then `00CC7081`. Thing `vtbl+20` is `004C72B0` stub. Mode push 0). `FirstSeenWalkToAppliesMove=false`. **`GamePause 0.8`** is the same 15× wait. Then **`WaitActiveDialog`** (`00CC656B`: no session `[ebp-44]==edi` → `00CC7081`; else poll `vtbl+1472` `008907D0`/`006E5660`. First-seen leftover InteractiveSpeak/DialogSpeak session → one `vtbl+28`). Dismiss UNREAD. **`UseCamera CAM_OVIF_SHOT3`**, **`Hero.Teleport MK_OVIF_HERO4`**, **`Hero.PlayAnimation ST_IDLE_SUBTLE,FALSE,TRUE`**, **`Father.Speak …70`**, and **`NoLoadUseCamera CAM_OVIF_SHOT4`** are already proven. Then **`Remove VILL1`** (`00CD0116`: empty name → `00CD17FD`; `dead` is unread `vtbl+1608`; else lookup + `vtbl+432` `008910D0` → `004C9B80` sets `[+145]` bit 0 / `[+146]` bit 2; `jmp 00CC864B`). No yield. Teardown bodies UNREAD — record only, do not invent a despawn mesh. **`Father.Speak …90`**, **`GamePause 0.2`**, **`NoLoadUseCamera CAM_OVIF_SHOT6START`**, and **`Father.LookToThing Hero,FALSE`** are already proven. Then **`Father.DialogadSpeak Father,'TEXT_QST_048_FATHER_INTRO_100'`** (`00CC3354`: empty actor/target/text → `00CC7081`; mode arg3 random=1 / norepeat=2 / sequence=3; table `00CD3187` hit → thing `vtbl+52` then `jmp 00CC707C`; miss → context `vtbl+280/+288` then `vtbl+52` and `jmp 00CC2C6B` = `0099EAE0` then `00CC7081`). No `vtbl+28`. Father `0x0127293C` +52 is `004CD1B0` `al=1`. No `00CBEE5E`. `FirstSeenDialogadSpeakDoesNotYield=true`, `FirstSeenDialogadSpeakAppliesUi=false`. Do not invent dialogue UI. **`GamePause 0.5`**, **`NoLoadUseCamera CAM_OVIF_SHOT6`**, **`Hero.SneakTo MK_OVIF_HERO5`** (FALSE wait), and **`GamePause 1.1`** are already proven. Then **`Father.LookInDirection 215`** (`00CC3F73`: empty actor/degrees → `00CC7081`; `0099E690` atof * `[0x1238E00]=1/360`; arg1 `00CBEE0C` IsFalse clears default flag 1; apply context `vtbl+1896` `0089BDF0`; **`jmp 00CC707C`**). No `vtbl+28`. First-seen has no second arg so flag stays 1. Heading body UNREAD — record only, do not invent a yaw write. `FirstSeenLookInDirectionDoesNotYield=true`, `FirstSeenLookInDirectionAppliesHeading=false`. **`UseCamera CAM_OVIF_SHOT7`** is already proven. Then **`Hero.SneakTo MK_OVIF_HERO5,0.0,TRUE`** (`00CC0CB5`: arg2 IsTrue takes `00CC0F1A` thing `vtbl+104` poll. Hero +104 is `006A9550`/`00661A40` leftover busy → one `vtbl+28` then idle `00CC7081`). Apply is still `004C72B0` — no mesh move. `FirstSeenSneakToTruePollsArrival=true`, `FirstSeenSneakToTrueYieldsOnce=true`. Do not invent arrival motion. Persist **vector 0 ends here** (`IntroCutsceneLastCommand`). Persist **vector 1** (def+72) is the seven-line skip list `FadeOut` / `GamePause 0.5` / `UseCamera CAM_OVIF_SHOT7` / two Teleports / `LookInDirection 215` / `FadeIn`. Vectors 2–7 are empty. Reader is **`00CC017C`**: `00CBEB7E` true and `[ebp-21]==0` then `0049B760` clear + `00432EE9` copy, set `[ebp-21]=1`. `00CBEB7E` is `[0x143E8F4]` ? `vtbl+168` `00894440` : `vtbl+176` `00893B00` (input codes `0x4B` / `0x2A` via `008ABED0`). First-seen skip is false (`FirstSeenCutsceneSkipFires=false`) so vector 1 does **not** run. `00CD17FD` end-of-list recopies **`+60`** only when `[ebp+120]==1`; it does **not** walk +72 (`FirstSeenCutsceneVector1AutoRuns=false`). Do not concatenate vector 1 onto the interpreter. Skip-key bodies UNREAD. Next unread is Create `008A9100` / Remove `004C9B80` mesh, skip-key bodies, AttackOver `+80`. `vtbl+1488` is **`008907E0`** on `CGameScriptInterface` vtbl `0x01260F0C`: packs **black `(0,0,0,255)`**, calls `vtbl+1492` → `006E7370` → **`00434C00`** (`[+188]=1` active, `[+201]=1` rising, `[+192]` seconds; if `[+216]` already set only `[+232]=0`). Then `[+216]=1` lock. Overlay draw is **`006496BC`** in `00648820`: skip when `[+188]==0`; else RGB from `[+212]`, A = **`004348D0`** × 255 (`004364C0` / `00BFEA70`). `004348D0` rising is elapsed/duration; after the tick completes +188 stays 1 so A=1. Tick is **`00434870`**. Bare `FadeIn` is **`vtbl+1496` `0088E4C0`**: clear +216 then **`00434C90`**. Record is **`0041BEB0`** type **`0x22`**, submit **`vtbl+92` `0xC0`**. Size pad `[0x125A298]=-8`. Live consume draws that screen-space color quad (`FirstSeenFadeOverlayDraws=true`). PlayAVI `vtbl+1476` is **`0088F890`** → `0040D2A0` then blocking **`006286F0(edx=0x1B)`**. `0099C1E0` rewrites wide **`.xmv` → `.wmv`**. Open `00A3B9D0` takes DirectShow when the path has `.wmv`/`.asf`. PC file is `data\Video\dream_sequence_comp.wmv` (ASF). Skip DIK **1/57/28/62**. Present is **`009DC870`** 2D submit then **`009D9C80`** flush; dest is letterboxed with leftover × **`[0x122F59C]=0.5`**. Letterbox dest (leftover × 0.5) is consumed. Open **`00A3B9D0`** CoCreates **`CLSID_FilterGraph`** (`0x12AB174` = `e436ebb3-524f-11ce-9f53-0020af0ba770`) + **`IID_IGraphBuilder`** (`0x12A9934` = `56a868a9-0ad4-11ce-b03a-0020af0ba770`), allocs renderer **`00A3B510`** size **`0x180`**, **`AddFilter`** vtbl+12, **`RenderFile`** vtbl+52. **`00A3B5F0`** copies `VIDEOINFOHEADER` biWidth / abs(biHeight) and RGB24 stride `((w+1)*3)&~3`; D3D texture formats **21 / 25**. **`00A3B130`** `put_CurrentPosition(0)` (`[0x122ED70]=0`) then **`IMediaControl::Run`** vtbl+28 retry **50**. **`DoRenderSample` `00A3BCF0`** is **`ret`**. Copy is C++ vtbl+172 **`00A3B730`** (not mid-fn `00A3B740`): `IMediaSample::GetPointer` (+12) RGB24 → **`009FA450` `LockRect`** (texture vtbl+76) format **21** (RGB+`0xFF`) or **25** (555), unlock **`009F9DE0`**, `SetEvent([player+124])`. Present **`006286F0`** `WaitForSingleObject` **33** ms then **`009DC870`** into the **existing game** backbuffer, dest leftover×0.5 (`FirstSeenPlayAviUsesVideoWindow=false`, `FirstSeenPlayAviUsesGetCurrentImage=false`, `FirstSeenPlayAviDraws=true`). `00A3B730` copies sample rows with no extra V flip. `IPin` is a **separate** heap object (`00CA6A30` alloc `0xE0`): CBasePin QI `00CA7CE0` compares IID `0x12A9B04` and returns **this+12** (vtbl `0x012BF3C0`). Filter +12 is `IBaseFilter` (`00CA7500` `add eax,12`, vtbl `0x0129D04C`) — not `IPin`. AddFilter therefore passes the IBaseFilter subobject. IPin QueryAccept `00CA84C0` calls filter C++ vtbl+176 **`00A3B590`**: `FORMAT_VideoInfo` + `MEDIATYPE_Video` + RGB24 only (RGB32 has zero code refs). GetMediaType `00CA84F0` is `E_UNEXPECTED` so EnumMediaTypes advertises **no** types. Connection CheckMediaType `00A3B5F0` is a different virtual (VIH + D3D format 21/25). `IEnumPins.Next` must write `IPin*`. Live EnumPins/FindPin return the heap pin (`00CA6A30` / `00CA4F40`), not the filter CCW. Controlled x86 vs x64 AddFilter+RenderFile on the same WMV **fails the same way** (`qa=0` `rc=0` `cn=0` `qpi=8`, Video Renderer selected) — not a 32/64 quartz difference (`playavi-arch/`). `00CA8420` QueryPinInfo writes `PIN_INFO` Filter=`IBaseFilter*` (C++ +12), dir at +4, name at +8. Live writes that native layout. SysWOW64 quartz Render `1008DE87` → ConnectDirect `1007F596` then QIs dest `IMemInputPin` **`56a8689d`**. Our pin had `56a8689c` (`IMemAllocator`) so RenderFile never QueryAccept'd. Fable pin C++ QI `00CA89A0` is `IMemInputPin`. Dest `EnumMediaTypes` yields the RGB24 type `00A3B590` accepts (`PlayAviQuartzDestEnumMustYieldRgb24`). GetAllocator `00CA5D50` returns the stored allocator (`S_OK`); Receive `00A3B730` GetPointer then returns — do not hold the sample. Present inverts V (`1-t.y`) so the bottom-up RGB24 DIB matches `009DC870` dest (v=0 at dest top). Do not grow a QI log after RenderFile. `00628A9E` is `WaitForSingleObjectEx([player+124], 33, TRUE)` — wait **up to** 33 ms for `00A3B8EB` `SetEvent` after GetPointer (`FirstSeenPlayAviWaitIsTimeoutNotSleep=true`). Receive is ~0.33 ms and is not the 33 ms pacer. Pace samples: `playavi-pace/`. Vulkan upload reuses persistent staging like `009FA450` LockRect (`FirstSeenPlayAviLockRectReusesTexture=true`). `009F9DE0` is `IDirect3DTexture9::UnlockRect` (`[vtbl+80]`) — it does **not** wait GPU idle (`FirstSeenPlayAviUnlockWaitsGpu=false`). `006286F0` later consumes the texture with `009DC870` 2D submit + `009D9C80` flush (`FirstSeenPlayAviUnlockThenNormalPresent=true`). Vulkan copies CPU pixels in `SetVideoFrame` (Unlock analogue) and records the image copy into the **existing** frame command buffer after `WaitForFences` — no per-sample `BeginOneTime`/`vkQueueWaitIdle`. `006286F0` then `009BEF20` BeginScene (`[dev+164]`), blit, `009BEF50` EndScene (`[dev+168]`), `009BEEB0` `Present` (`[dev+68]`). That apply does not return, so `00A44880` does not tick fade or resume other fibers (`FirstSeenPlayAviBlocksUpdatePump=true`) and does not draw 3D (`FirstSeenPlayAviDrawsWorld=false`). WaitEx waits for the next sample (or 33 ms). `009BEEB0` Present is the swap — Mailbox/FIFO like D3D vsync, not Immediate 30 Hz flips. `00A44880` does not run during the apply. Samples: `playavi-present/`. Leftover QI `00CA4780` is `IMediaPosition`/`IMediaSeeking`. Do not advertise RGB32. Ctor CLSID `0x129D150` = `71771540-2017-11cf-ae26-0020afd79767`. Do not steal the Video Renderer or `Connect` around RenderFile. `trace-quartz` persists that path. Present wait **33** ms is the game blit tick, not a decoder throttle. IMFSample / popup `IVideoWindow` are DISPROVEN. No `vtbl+28` — the apply owns the pump. First-seen `.Teleport` consume is **`0089B780`**: marker pos **`004AA980`** (`[handle+4].vtbl+24`), then **`[thing+96].vtbl+124(pos)`**. Apply site **`00CC47B4`** also calls yaw **`004AAA40`** (`vtbl+40`, default `[0x122DEDC]=0`) and **`0089B780`** later **`vtbl+1896`** (`0089BDF0`) with that float (`FirstSeenTeleportReadsYaw=true`). `0089BDF0` either `[thing+96].vtbl+264(yaw)` or look-at **`00753E90`** after `yaw*[0x124F2B8]` — both unread as a mesh rotate (`FirstSeenTeleportAppliesYaw=false`). `00DB86B0` binds **`Hero`/`Father`**. Kid moves from `NOVStartHSP` (34.45, 129.03) to **`MK_OVI_ID_HERO` (38.92, 131.88)**; Father TNG has no pos so **`MK_OVI_ID_DAD` (39.29, 130.65)** instances the mesh. Region-change unread. Live path: `WorldSceneTests.New_game_intro_runs_through_generic_script_runtime` / `First_seen_Teleport_moves_kid_and_places_father`.
**Intro child-return continuation (PROVEN).** After `00CBFB7D` returns from `CS_OAKVALE_INTRO_FATHER`, native `S_QNOVI` runs `00DB88FD–00DB8946`: `vtbl+1484(0)` at `00DB890C` clears the child cutscene presentation, `vtbl+2664(0)` unmutes sound, `[quest+82]=1`, `vtbl+1504(1.0)` performs the scaled fade-tail wait, then `vtbl+1668(0)` / `vtbl+1664()` release the scripted camera. `vtbl+1504` is `0088E4F0` → `006E71F0`: target = `0049D870() + [0x1375550] * seconds`, then microthread `vtbl+8` is pumped until `0049D870()` reaches the target. It is a 15-WorldFrame wait, not a render-dt/wall-clock deadline. Camera release does not prove a stored gameplay snapshot: the normal `0049E080` → `006B42F0` world-camera pass owns the following view. Native then immediately enters `00DB894C–00DB8A3B`: platform branch, `vtbl+460` submits `TEXT_QST_048_INSTRUCTION_HIGHLIGHTING[_PC]`, and `vtbl+160` (`00894370`) queries the world-event journal through `008ABED0` for event type `0x12`, registered by `0073B6FE → 00742590 → 0073F310` as **`CHEERING`**. The query filters record `+8 == 0x12` and record timestamp `+52` against the active time window; it is not an input-button query. Native yields through `vtbl+28` until that journal predicate succeeds. Only then does `00DB8A83` create `HUD_DEED_GOOD_ICON` through `vtbl+1308` and enable it through `vtbl+1284(1)`. This is distinct from the later PostAttack player-control handoff, which remains UNREAD. The managed lifecycle must preserve this order; do not replace it with a global queue, a keypress, or an instant generic handoff.

**Intro father-loop resource boundary (PROVEN).** The two calls at `00DB8B00` and `00DB8BBE` are CGameScriptInterface vtable slot 8, `0089B5B0`, with mode values 3 and 4 around the `SCRIPT_NAME_HERO` test at `00DB8B6A`. RTTI resolves the queued object's vtable `0x0128D86C` to **`CScriptGameResourceObjectScriptedThing`**. Constructor `009039D0` stores the mode at `+28`, binds scripted-Thing component type 31 through `00712C20`, and the object is queued through `008AB960`. `0089B5B0` rejects inactive/removed Things and yields while component-31 `+24` is busy; success means the resource wrapper was queued, not that a game mode changed or player control was granted. After mode 4 succeeds, native enters the father/good-deed loop at `00DB8C0C`. That loop runs through `00DB972F`, branches on quest counters at `+84/+88`, polls dialogue handles with `vtbl+28`, tests inventory/reward paths including `OBJECT_CHOCOLATE_BOX_UNGIVEABLE`, and loops back to `00DB8B00`. Its complete managed branch bodies and the later PostAttack player-control owner remain PARTIAL/UNREAD; do not rename modes 3/4 as control states or use them as a handoff.

1. **Full game.bin field tables.** We read `Graphic.bank_index` and `CReplaceableMeshDef`. Other controls (health, physics, inventory, quests) are still raw bytes.
2. **Creature clothing / appearance layers.** Villager Graphic is the unclothed body (`MESH_BS_MALE_MIDDLE_UNCLOTHED_01`). `CAppearanceDef` / morphs are unread.
3. **Streetlamp lit vs off.** TNG `OBJECT_STREETLAMP_LIT_SINGLE_01` maps to `MESH_OBJECT_STREETLAMP_OFF_02`. Lit state is probably a replaceable / particle, not a second mesh id.
4. **Tile extra-object formats / 7-byte extras.** Edge-strip `fmt` values (`0x5901`–`0x5904`, `0x5801`–`0x5804`, `0x7900`, …) are unread as side/LOD flags. The 7 bytes after Z are still packed 11-11-10 + RGB.
5. **GPU texturing leftovers.** Two albedo samplers, no atlas. CreateTexture is DXT FourCC + **SCRATCH** pool. First-seen 414 / 4130 / 3172 store framed-LZO **top mip** plus **raw 256..4** (`PayloadHasRawLowerMips`; not top-mip-only, not a 1×1 chain). Decode still uses the top mip. Sampled DEFAULT format after SCRATCH, `[+452]` levels default, and LockRect-per-level of the leftover are unread — do not invent GPU BC or generated mips. House-area C3D **does** store bump ids (rugs **1740**, books **2315**; `FirstSeenHouseAreaC3dStoresBump=true`) but first-seen static-lit `00BB30A0` pushes texture-count **1** and `PSHADER_TEXTURE_DIFFUSE` samples only `t0` (`FirstSeenBindsC3dBump=false`, `FirstSeenStaticTextureStages=1`). Do not bind those bump maps on the first frame. No shadow-buffer / colbuff variants. Layer-type UV flip: per-cell `00BF5175` `cmp ebp, 4` then `00989A60(1)` writes `(1,0)` (one layer) or `(0,-1)` (two). Type **4** is water enqueue `00BF44B3`. First-seen FG is not type 4 (`FirstSeenUploadsC1LayerFlip=false`). `VSHADER_LANDSCAPE_FOREGROUND` does **not** read c1 (`FirstSeenLandscapeVsReadsC1=false`). **FG oT0.xy = v3.yz** is ExtraRgb.YZ (~0.5) for t0 alpha. **BG oT0 = v3** so bit-4 samples ExtraRgb.XY. Live verts carry ExtraRgb and the mesh shader swizzles the same way. **Albedo oT1** is `dp4(pos, c40/c41)` with first-seen **c40=c41=(0,0,0,0)**. `00989A60` has **18** `E8` sites; first-seen landscape uses slot **2** (`00BF5150`), slot **3** (`00BF5170`), and slot **1** only for layer type 4 (`00BF51D4`). None push 40/41. `00B674B3` `[esp+40]=0` is a stack offset. LayoutBasic fields are 3/18/4/5/9/2/11/15/16/19; LayoutRepeatedMesh adds 51/67/19/35/83 — no field 40. `007E17AB` copies runtime +48 onto `[esp+36]` when +45≠0; that slot is not a proven mesh scale (`FirstSeenMultiStaticValueIsScale=false`). File→runtime +44/+52 persist is **004EB8C3** (FlagA/FlagB/Value/Tail → +44/+45/+48/+52). First-seen skip-global is **−1**, so skip stays off. `UvScale=0.125` is the c3 lighting table only. Heightfield `pos*0.125` UV was a stand-in — first-seen tiles emit `ProjectOt1=(0,0)`. Stages 2+ stay unread. FG `oD0.w` is `(dp3(r2,c42)+c42.w)*v3.x` (`FirstSeenForegroundOd0WUsesC42=true`). Zero `push 42` / `def c42` / layout field 42 on the first-seen path (`FirstSeenWritesC42=false`), so D3D default **c42=0** and **oD0.w=0**. Landscape alphablend is off so RGB still draws. Static / PALSKIN / landscape BG are **`mov oD0.w, c0.y`** (first-seen **1**) and do not read c42 (`FirstSeenStaticOd0WIsC0Y=true`). Live FG uses that 0; BG/static/PALSKIN use 1.
14. **CPatchTesselationEdgeStrip fmt.** Extra objects parse as self-contained PrimitiveCount+2 strips. What `0x5901`–`0x5904` / `0x5801`–`0x5804` select is unread.
15. **LoadWaterData / CEngineWaterRenderer mesh.** Draw `00B783F0` empty-out is now a shipped gate (`WaterDrawShouldSubmit_00B783F0`): all vector begin==end and not (`[+630] && [+645]`) → `je 00B7851D` → `00B7A865` bare `ret 4`. Second gate `00B72180` is only `E8` from `00B78584` (`FirstSeenWaterDrawReachesSecondGate=false`). First-seen ctor zeros → `FirstSeenWaterDrawShouldSubmit=false`. Landscape `TryResolve` omits `SEA_*`/`WATER_*` through that gate. Prepare `00B71FB0` always clears the draw vectors then bind (`FirstSeenWaterPrepareFillsMesh=false`). Type-4 enqueue `00BF44A0` is per-cell only.  `OpenStaticMaps` mode 1 (`00B4282B`) copies `__ENGINE_SEA_STATIC_MAP_BANK_FILE__` onto the water renderer (`00B6D4D0`, store +1448 / +0x5AC) then looks up `__STATIC_MAP_COMMON_HEADER__` (`0x1436ECC`). `LoadWaterData` (`00B41FA0`) looks up `__ENGINE_WATER_STATIC_MAP_BANK_FILE__` (`0x1436EC8`); missing intern jumps to `00B420E4` (no ingest). When a bank exists, `00B6DAF0` reads the first u32 and calls `00B6D6E0`: **`cmp eax, 8` / `jne` → `xor al,al; ret 8`**. Type **8** copies the next two u32s to `[this]`/`[this+4]` and returns 1. StartOakVale has **no** water-prefix STB bank; sea first u32 is **7363**, so `IsLoadableWaterBank` is false. Draw `00B783F0` ORs emptiness of vectors at +508/+512/+520/+524/+532/+536/… and **`je 00B7A865`** when all are empty (`00B7851D`). That out is a **bare `ret 4`** (`FirstSeenWaterDrawEmptyIsBareRet=true`) — no unbind. `LoadWaterData` is only called from `00B429CB`; first-seen intern miss is `00B420E4` (`FirstSeenLoadWaterDataFindsIntern=false`). First-seen landscape no longer emits `442`. Type-8 ingest in `00B6D6E0` consumes **exactly two u32s**. Water ctor `00B73760` (vtbl `0x12A3364`) **zeros +508..+624**. Draw `00B783F0` (2341 insns) ORs begin==end on those vectors plus `+630`/`+645` and **`je 00B7A865`** (`00B7851D`); a second empty out is `00B72180` `test al` / `je 00B7A865`. Missing water intern is `00B420E4` `pop; ret`. Sea name `00B6D4D0` does not write those vectors. `7363` is **not** a water type (only imm is KnotholeGlade/Arena). `FirstSeenWaterDrawIsEmpty` is **true**. Type-8's two dwords are copied to a **stack local** in `00B6DAF0` (`lea ecx,[esp+12]`) and **discarded** (`WaterType8DwordsAreStoredOnRenderer=false`); `LoadWaterData` ignores `al`. Sea intern is stored at **+1448** / name at **+1452**. Bind `00B6DC40` requires `[+636] ≠ 0`; ctor `0099E4B0` writes **0** there (`FirstSeenWaterWantedNameIsZero=true`), so first-seen bind returns 0 without reading the 7363 bank (`FirstSeenSeaBindRuns=false`). The only assigner is **`00B23F00`** (`[0x1436E54]+636 = 0099EFB0(arg)`), vtbl slot **14** at `0x012A1038`. Sibling `00B23900` is `this+636`. Zero `E8` callers; zero `call [r+0x38]` in OpenStaticMaps / landscape-water / MainScene / StartOakVale (`FirstSeenCallsWantedNameSetter=false`). `00B71994` is the **dtor** (`0099EAE0`), not a writer. `+1464` / `+630` stay ctor-zero. Draw `00B783F0` mesh-ready is **`[+630] && [+645]`**; first-seen both 0. When bind would run, `009D6100` / `009D5DF0` wrap the whole 7363 blob in a **72-byte** stream — **no type-8 check** (`SeaBindUsesType8Check=false`). Stream reader `00BE91E0` (245 insns, only `E8` from `00B6D420` at `00B6DECD`) then pulls word **0** as vertex count, word **1** as index count, word **2** as vertex payload bytes, word **3** as index payload bytes (stride **12**, `D3DFMT` **101**). `00B6D420` writes PrimitiveCount at **+180** = indexCount−2 and sets **+630=1**. First-seen `00B6DC40` returns at `00B6DD06` when `[+636]==0`, so those words are never consumed (`FirstSeenReadsSeaPrefixWords=false`, `FirstSeenCallsSeaMeshCopy=false`). StartOakVale prefix is **7363, 44259, 58022, 1135617, 4340736**; word 4 is the first payload dword (UNREAD as a field). Water vtbl[1] `00B71FB0` clears +508..+620 then one `00B6DC40`; vtbl[2] `00B6D500` is `ret 4`. Type-8 dword *values* still UNREAD (never stored). Layer type **20** (`00BBC130` table) is the NONE-draw pass; Flag1 is not proven to select that type. Flag1 is **not** the first-seen NONE selector (`FirstSeenAppliesCullNoneFromFlag1=false`); two-sided meaning still UNREAD.
16. **Picnic fillers.** `PicnicArea_Filler_02/03` touch Lookout, are Lookout `SeesMap`, and **do** have STB `.lev` (2.6 MB / 407 KB). `LoadedOnPlayerProximity FALSE`. Previously skipped by an invented `FindLev` name filter.
17. **CEngineLightingManager register maps.** `CEngineVSConstantLayoutLights` ctor `00BDB400`: **`[+84]=19`**, `[+76]=2`, **`[+96]=35`**, `[+100]=1`, **`[+108]=31`** count **4**, **`[+20]=38`** count **58**. Flush `0098A540` uploads light *i* at `c[19+2i]`. Apply `00F39D40` writes dirlight **c19/c20** then `+32=1` / `+36=0` and `ret 4` (`FirstSeenLightApplyWritesC35=false`). **c35 first-seen** is the `0098B2C0` stack default **(0,0,0,1)** (`FirstSeenC35IsSetterDefault=true`). First-seen FG/static/PALSKIN VS have **no `LIT`** (`FirstSeenVsUsesLitC35=false`); they `MAD c35`. PALSKIN packer `00BD2D90` rebuilds dest when `+288==0` (`FirstSeenPalskinPackerRebuildsWhenDestNull=true`); no play-anim so dest = identity. Water `00B783F0` empty-out `00B7A865` is a bare `ret 4`. Camera `00B314E0` `+536==0` is the helper path (`FirstSeenUsesThirdPersonView=false`). Gather `00B46280` (cap 8, per-cell asks 5) is **enabled** (ctor `[+204]=1`; `[+204]==0` returns the empty list at `+17644`). It walks a 16×16 pointer grid at lighting `+236` (ctor `rep stosd` 256 slots). Pack `00B44F20` writes slot `1..n` via `00989830` and disables leftover slots with `00988640`. Record pack `00B49320` copies pos + colour + `1.0`. **c31 flush `0098A6F6`** is `SetVSConstantF(c31, [wrapper+416], count)` only when packed count **> 1**. `VSHADER_*_DIRLIGHT_FOG` (first-seen bind names) do **not** read `c21`/`c31`. `VSHADER_*_2POINTLIGHTS_FOG` / `VSHADER_LANDSCAPE_FOREGROUND_2LIGHTS` read **c21**. Family ctor `00BB5040` / landscape `00B69000` resize a 6-slot vector (`00B6CBD0`, 8-byte records). Stores: slot **0** `*_DIRLIGHT_FOG` / `VSHADER_LANDSCAPE_FOREGROUND`, slots **1–2** `*_2POINTLIGHTS_*` / `_2LIGHTS`, slots **3–4** `*_4POINTLIGHTS_*` / `_4LIGHTS`, slot **5** `*_5POINTLIGHTS_*` / `_5LIGHTS`. MainScene `00B34619` allocs **0xF0** and calls `00BB5040`; **+32..+52** (6 remap dwords) stay the ctor zeros — no refill at the construct site. Draw `00BA2606` does `count = min([list], 5)` then `remap[count]` then `00988020` → `SetVertexShader` (`[dev+368]`). Remap-all-zero ⇒ **slot 0**. Init bind `00B8B660` is the 1-light name. Packed count ctor **`[+160]=0`**. Add-light is lighting vtbl[7] `00B481E0` message **16** → `00B480E0` (reject `[+112]`/`[+120]` &lt; **0.1**, colour channel &lt; **1/255**). Indoor `MARKER_LIGHT` (130,60,5) / radii 8, 9 **pass** those compares, but `0089FAA8` does **not** call `00B480E0`. Collect `00B47BC0` / grid rebuild `00B46660` only walk the master list at **+184**. First-seen packed count is **0** — do not bind 2LIGHTS or invent a point-light term. `[+18068]` is a **1-or-2 mode** (ctor **1**; setter `00B23C00` arg0→1 / else→2; 0 `E8` callers), compared to dirty byte `[+206]`, **not** the slot index. First-seen PALSKIN pass **4** skips the `[+28]` jump table. `00989A60(0)` / slot 33 does **not** run. Default path **does** `SetVSConstantF(c38, bone float4s, n*3)` via `0098B930`. Fog: LayoutBasic **`[+56]=18`** count 1; ctor record **`+64=(0,0,0,1)`**, **`+80=1000`**, **`+84=2000`**. First-seen **c2** is the `00B47630` linear view-Z plane (`FirstSeenFogC2IsLinearViewZ=true`). `00B54310` inverse row 0 is not first-seen. Live client consumes that plane and the `dp3 n,-c19` square term. First-seen FG / static / PALSKIN / inner-sky VS **only `dp4 oPos` `c5–c8`** — the `00988A50` product at wrapper+752 (`FirstSeenVsReadsSeparateWvp=false`). Registers 9–16 are unused on those programs. Combined `ComposeWvp` is that product. Landscape-only `T(cam)` is already live. PALSKIN bone VS on the GPU is still bind-pose equivalent.
18. **Sky mesh leftovers / weather / local detail.** Dome ellipsoid + cap/skirt + colour + first-seen UV **(0,0)** are locked. Colour is RGB white with per-ring alpha `max(1 − 1.105 cos(elev), 0) * 255` via `00BFEA70`. Divisor `[0x0143782C]` is CRT `13000 * cos(qword 0x12A1140)` ≈ 11583. `this+16` / `this+20` are empty **CString** first-dwords on **`ENVIRONMENT`** +292/+288 (ctor `0099AED0` zeros; persist `004310A7`). Rain/snow/mist and `CEngineLocalDetailGenerator` are not drawn. Star draw **`00B65A20`** is called from first-seen mesh draw **`00B66190`** at **`00B66284`**. Hop is `[0x1436E8C]+408` then two vtbl+4, then `D=[C+84]`. **`[D+424]`** is a pointer to two dwords; first dword **`== 0`** takes **`00B65A87 ret 4`**. That dword is then an index into a **44-byte** record table (`imul 0x2E8BA2E9`), not a `stars.dat` walk (`FirstSeenStarDrawIteratesStarsDat=false`). Map-manager **`+424`** is OpenStaticMaps **mode** (0/1/2) on a different object. Fade is **`1.0-*([D+436])`** uploaded as VS colour via wrapper+80. Sky ctor writes **`[this+396]=1`**, so **`00B66190`** also calls weather **`00B64FA0`** at **`00B6629D`**. Weather loads **4** graphic ids from **`[D+472][0/4]`** and **`[D+448][0/4]`**; each `id==0` increments a skip count; skip **4** takes **`00B659A5 ret 4`** (`WeatherAllZeroIdsSkipDraw=true`). After a non-zero id it only uploads fade/colour and dirties device slots — **no new mesh** (`FirstSeenWeatherDrawBuildsMesh=false`). Invented Lookout-centered `stars.dat` billboards at `(64,64,0)*1700` are removed (`FirstSeenEmitsInventedStarBillboards=false`). NString ctor `0099E4B0` writes first dword **0**. Theme-slot ctor **`008864A0`** zeros **`[this+424]`** with the +308..+424 block. Theme copy **`008865C0`** at **`00886AD2`** writes **`dest+424=[src+192]`** and treats dest+428 as a vector; only `E8` is the six-slot loop **`00888499`**. `D` is `[C+84]` after the map-manager hop, **not** ENVIRONMENT +424 (inline NString). First-seen pointer first-dwords therefore start **0** (`FirstSeenStarListPointerCtorZero=true`); a non-zero `src+192` on first-seen is unread (`FirstSeenStarPointerPayloadsAreNumericIds=false`). The empty-id skip gates stay live.
19. **C3DMeshLODInfo / boolean-alpha / two-sided.** RTTI `C3DMeshLODInfo` vtbl method `00A23DE0` (slot `0x0129CDB4`, zero `E8` callers): **`[this+36]==0` → `mov al,1; ret`** (`FirstSeenLodInfoNullReturnsReady=true`). That is not a mesh-id swap (`FirstSeenLodInfoSwapsMesh=false`) — first-seen house **6909/6911** and kid **4300** stay those C3D ids. Non-null `+36` jumps to **`009D54E0`**, which locks `[this+40]+0x14C` via `00A62920` then returns `[this+68]`. Landscape tile expand `00BF3BEC` and patch submit `00BDC6D9` call `009D54E0` on the resource object directly (skip expand when `al==0`). Do not invent a second C3D LOD mesh. `CStateBlockFunctionAlphaBoolean` / `CEngineStateBlockAlpha` write device slots at +10524/+10544. CULL table `0x01396FB0` is **3 / 2 / 1 / 0** (CCW / CW / NONE / 0). First-seen landscape `00B24850` / static-lit `00BB2540` **do** set `D3DRS_CULLMODE` to **3** (`D3DCULL_CCW`) via `0x01396FB0` with **no Flag1 test**. PALSKIN bind `00BD3070` / draw `00BD71B0` **do not write CULLMODE** (`FirstSeenPalskinWritesCullMode=false`); they inherit landscape CCW. They do set **`D3DRS_SPECULARENABLE=1`** at slot `+10724` (RS `0x1D`); first-seen VS has no `oD1` so the FFP addend is 0. MainScene `00B33010` first-seen layers `0x80`/`0x100` do not call NONE-pass `00B89C30`. Static `00BA2350` Flag1/`+12664` is only after `cmp pass, 2` (`FirstSeenStaticPass2ReadsFlag1=false`). `0x01396FB8` = NONE is applied **unconditionally** at the start of other primitive passes (`00B89C30` at `00B8B0F7`, `00BBE090` at `00BBE717`, `00BC3F30` at `00BC4060`) and **restored to CCW** after the draw (`00BBE8BB` / `00BC4AEF`). Layer switch `00BBC130` (`[arg+8]-7` jump table) routes one case to `00BBE090`. C3D **Flag3=1** is `DegenerateTriangles`. Serialize `00ABF6B0` stores Flag1 at **`C3DMeshMaterial+41`**. **Flag1=1** on kid hair / house 3180 is **not** the first-seen NONE selector (`FirstSeenAppliesCullNoneFromFlag1=false`). Layer type **20** selects NONE-draw `00BBE090` (only `E8` is `00BBC210`). First-seen PALSKIN `00BD71B0` (kid hair 793) tests `[this+8]` as an enable byte and never calls `00BBC130`. First-seen static-lit `00BB2540` (house 3180) never calls it either. Zero `mov [reg+8], 20` in `00A00000–00C00000`. **`FirstSeenFlag1WritesLayerType20=false`**. Kid hair `MapFlags=1` like face/torso; only hair has Flag1=1. First-seen PALSKIN bind (pass 4 through `00BD35ED`) writes **D3DRS_SRCBLEND=5** (`0x01396F78` SRCALPHA) and **DESTBLEND=6** (`0x01396F7C` INVSRCALPHA); slot init `00A047EA`/`00A04825` is RS 19/20. Alphablend enable `+10424` is 1. **No Flag1 test for blend** (`FirstSeenPalskinSrcAlphaBlend=true`, `FirstSeenFlag1SelectsAlphaBlend=false`). First-seen PALSKIN **draw** at `00BD76D2`/`00BD7705` **does** read +41: if Flag2==0 and Flag1!=0 it `or ebx, 5` (`FirstSeenPalskinReadsFlag1=true`, `FirstSeenPalskinFlag1MaskOr=5`). Instance `+39` is ctor **`0xFF`** (`00B991F5` / `00BBBE7D`), so the Flag1 loop is not skipped. Helper ctor `00BCE740` stores the type index at **`+28`** (hair MapFlags=1 + mask 5 → **4**), not `+24` (`+24` is arg1, released by `00BCE4CB`). First-seen bind pass 4 (`00BD549D`) does **not** read helper+28 (`FirstSeenPalskinBindUsesHelperTypeIndex=false`); `00BD3B1A` jump table uses bind-arg+28. Draw queues via `00B84720` on `0x1436E74`: `[inst+104]+8==1` → slots **10** then **14**; type 0 → slot **8** and Flag1 adds **9**. MainScene `00B33010` drains slot 14 at layer `0x80` and slots 8+10 at `0x100` through `00B849F0` (`[helper+20].vtbl+20/+24`). First-seen static-lit `00BB2540` has no +41 read (`FirstSeenStaticLitReadsFlag1=false`). A later static path `00BA2350` at `00BA3637` skips applying `0x01396E58` (=1) to device `+12664` when Flag1!=0 — that slot is not first-seen CULLMODE `+10384`. Skinned C3D (kid) draws with SRCALPHA; house 3180 has no bones and stays opaque. Drain `00B849F0` on PALSKIN vtbl `0x012A78DC` calls **+20 `00BD7110`** (queue+20 byte 0) or **+24 `00B91340`** (debug unwrap). `00BD7110` with helper+32==0 calls `00BD3070(helper, arg1)`. Jump-table case 4 (`00BD3C04`) needs `[ebp+124]==2`; drain arg1 is queue+12, not 2, so first-seen stays on default `00BD549D` (`FirstSeenPalskinDrainUsesType4=false`). Type 4 is stored on the helper but not bound on the first frame.
19. **Water / sea are not landscape FG.** STB has 25 `__ENGINE_SEA_STATIC_MAP_BANK_FILE__<Region>` blobs (StartOakVale 129966 bytes; first u32 **7363**, not type 8). No `__ENGINE_WATER_*` STB entries. WAD/STB `.lev` has no `ENGINE_WATER` string. StartOakValeWest cells include `SEA_OAKVALE_*` and `WATER_GREYCLIFF_ET` / `WATER_BWLAKE_*`. Token-scoring those onto `LANDSCAPE_CLIFF_RUGGED_*` was invented — textures.h has no `LANDSCAPE_SEA_*`. Real water textures are `LANDSCAPE_WATER` 442, `WATER_SURF` 473, `WATER_EDGE_ALPHA` 472, bump 476/477. `PSHADER_WATER_FOREGROUND` is 2 `tex` plus ops `0x47`/`0x48` (`texbem` family), not landscape `mul_x2`. Landscape tiles that are only sea/water are omitted until the water renderer is fed.
6. **WAD cell bytes 4–7 / 14–20.** High-entropy field and flags after the material slots are unread.
7. **Animation / bones / cloth.** C3D bone blocks are parsed (exe serialize `00A894ED`–`00A8955F`): after the `u16[count]` name-offset table, framed LZO **names**, **60×N** (id / parent / flags + 48 unread bytes; getter `00A4BD70` is `base+156 + i*60`), **48×N** local quat+translation+scale (mesh+160), **64×N** row-major 4×4 at mesh+224 (`shl eax,6`). Kid **4300** has **76** bones; bone 0 `Scene Root` parent −1 is file identity; `Bip01` parent 2 last row is (0,0,0,1). First-seen VS family is PALSKIN. Drawn verts go through first-seen PALSKIN: UBYTE4 indices+weights after pos (kid stride **28**, flags `0x14`), palette = hierarchy(48-byte local TRS, parent from 60-byte) × 64-byte inverse-bind (`00A9E1E0` + `00BD2F91` `dest = S*C3D`). That product is **identity** with no play-anim, so positions match bind-pose. Packer `00BD2D90` rebuilds when `[this+288]==0`. Detector `00A5B850` is `CPUID(eax=1)` then **`[0x13D2880]=1`** on SSE (`FirstSeenSseMatrixFlag=1`), so first-seen takes the SSE `mulps` dest=`S*C3D` path; `00BD2F91` is the x87 fallback of the same product. Kid **4300** all **76** first-seen palettes are identity. The consuming component is **`CTCGraphicAppearanceMultipleStaticMeshes`** (name `007E1A80`, factory alloc **0x5C** + ctor `007E14C0`). File fa/fb/f → runtime +44/+52 persist is **004EB8C3** (`FirstSeenMultiStaticPersistMapsFileFields`). First-seen `[0x13756F0]=-1` so skip is unused on SHOT2. `00BCFB00` copies `movzx esi, [block+24+i]` (ExeIndex used to misread that as `dh`) ×64 from the packed dest. Bind switch **`00BD3070`**: `cmp [ebp+124], 2` then `jmp [0xBD5C40+([arg0+28]-1)*4]` (18 cases; 5/14/15 are 0; case **16** = `00BD42CD` slot-33 `c38`). PALSKIN helper `00BD6810` → `00B32E90` pushes pass **4**; that is **not** 2, so first-seen takes default **`00BD549D`** (also the fall-through for any pass other than 1/2/8/9/10/12/13). Default calls bone pack **`00BD2D90`** (64-byte records, `shl eax, 6`) then **`00BCFB00`**: copy **12 dwords / 3 float4s** per influence (`lea eax,[eax+eax*2]`) and **`0098B930`** = `SetVSConstantF`. Start register is `[0x1436E98]+11860` = derived layout at shader `+11644` field `+216` = **38** (`00BDB260` `mov [esi+216], 0x26`); `+220` = **54**. Draw is `[dev+328]` `DrawIndexedPrimitive`. `00989A60(0)` / slot 33 still only on pass-2 case 16 — `FirstSeenUploadsPaletteC38` is **false**. `FirstSeenBoneUploadWritesC38` is **true**; the float values are the packed bone records, **not** invented identity. LayoutBasic `00BDBB70` second float4 is `0x43800000`×4; dirty-2 flush `00989BF0` (count **2** from register 0) therefore uploads **`c1=(256,256,256,256)`** (`FirstSeenC1`). First-seen PALSKIN VS is `mul r2, v1.zyxw, c1`; `mov r3, v2.zyxw`; `mov a0.x, r2.x`; `mul/mad r, r3, c[38+a0]` (const src bit `0x2000`) (`FirstSeenPalskinUsesA0RelativeC38=true`). Both streams use the same `.zyxw` so bone/weight pairs stay matched; `SkinPosition` file-order blend is that product. First-seen palettes are identity, so GPU a0/c38 equals CPU bind-pose. `009896D0` still never pushes offset 0. `00DBDE40` / Create `006AC910` / ConstructFromParams `006A9DD0` / parent `00662880` / `008388D0` / `006A5950` / activate `004C9CA0` have no play-anim. `STAND` has zero code xrefs. `.PlayAnimation` is `00CC14B8` (token; `00CBFACA` is the comma splitter, not the start). Interpreter ctor `00CBFD57` writes `[ebp-22]=1`, so first-seen PlayAnimation is **YieldAfter** one `vtbl+28` (`FirstSeenPlayAnimationYields=true`). Host records actor+name+five flags. Thing `vtbl+72` is **`004C7470`** (component walk → `[comp.vtbl+68](name)`). CTCAnimationComplex **`+68`** is **`00686920`** stub; inner play **`0070D580`** is not on that path (`FirstSeenPlayAnimationAppliesPose=false`). Kid create still has no PlayAnimation. **`FirstSeenPlaysAnim=false`**. Flag1 is not the first-seen NONE selector. First-seen PALSKIN draw reads `C3DMeshMaterial+41`, ORs **5**, and stores type index **4** at helper `+28`; bind pass 4 does not consume that index. First-seen drain does not take jump-table case 4 (`FirstSeenPalskinDrainUsesType4=false`).
8. **Hero, combat, quests, UI, audio.** Frontend UI defs and cutscene defs parse as GameBin entries; fields inside are unread. `.lug`/`.lut` payloads, `.ogg`/`.wmv`, tattoos, and `stars.dat` channels are unread.
9. **BWD second blob.** After the 398 region records (~22 KB). The 9-byte trailer is now MapUID plus 5 unread bytes.
11. **BWD leftover header.** `u32=142` plus ~16 integers before the first display name. Graph names such as `GreatwoodEntrance` are not always a WLD `LevelScriptName`.
13. **BWD extra integer lists.** After the named neighbours, remaining u32s (edge indices?) are unread.
12. **Ten exits pack a MapUID that is not in WLD.** Dest file missing; slot rule still holds when the map exists.
10. **text.big after the first UTF-16 string.** Some entries append a `.lug` name / extra binary.
20. **WVP leftovers.** View source is camera+128 (cot-scaled helper 3x4). `00B314E0` builds **`right = normalize(up × look)`**, packs **`(right, look, up)`** at helper+16, does **not** Gram-Schmidt look against up, then `00B30B50` writes `t = -dot(axis, pos)` and cot-scales rows 0/1. That is **not** `CreateLookAt(right, up, -forward)` (`FirstSeenViewUsesCreateLookAt=false`). Landscape world is `T(cam+84)`; bind/static world is identity. Live client composes `p * W * V * P` with that helper view. First-seen VS consume **only c5–c8** (`FirstSeenVsReadsSeparateWvp=false`). Sky proj is the else-path 100/10000 / 0.99–1 slice. House floor **3184 has no C3D prims**; first-seen interior is multi-static **6911** (int walls 3172) plus landscape **4130**.

First-seen DX9→Vulkan submit contract, path audit, and status
matrix live in `docs/render/` (`FIRST_SCENE_CONTRACT.md`,
`FIRST_SCENE_AUDIT.md`, `DX9_VULKAN_PARITY.md`). Translations are
`src/Fable.Render/Parity/Dx9Vulkan/`. Screenshot matching is not
the parity oracle.

## Exe load / render pathway

Traced in `Fable.exe` with `tools/Fable.ExeIndex` (`disasm` / `calls` / `trace-render`). Do not invent steps. `map-newgame` / `trace-newgame` writes every function reachable
from New Game / `StartOakVale` seeds **and every real function prologue in those
first-scene code ranges** into `newgame-trace/fnmap.md` (Lookout and the rest of
the exe are not scanned). Callees outside the ranges are recorded on the caller
only. Finish a cut first-scene function (PALSKIN bind `00BD3070` was split at
`00BD4000`) before widening the window.

1. **`CTextureManager`** reads the 34-byte BIG info. `u16` at +12 is the format code (`31` / `32` / `35`).
2. **Framed LZO** inflates the bank payload. Format 35’s first frame is **262144** bytes (`512×512×16`) — one DXT3/DXT5 top mip — not DXT1’s 131072. Last 3 bytes of the frame are the raw tail (`DecompressFramed`).
3. **CreateTexture helpers** `009BE800` (DXT1 probe), `009BE830` (DXT1 + name), `009BE870` (DXT3 + name), `009BE8B0` (DXT1 + name) call `IDirect3DDevice9` vtable `+40` with FourCC, **pool `3` (`D3DPOOL_SCRATCH`)**, usage `0`, levels `[wrapper+452]`, height `[+92]`, width `[+96]`. Table walk `009BE590` uses the same args with format from `0x129BA40`. **`00416C8A` is `Init Graphics`**: probe `009BE800`, on success `009BE830` / `009BE870` / `009BE8B0`, then **`00416D20` CString `DXT5`** (`FirstSeenInitGraphicsDxt5NameIsCreateTexture=false`). Same function later pushes `Opening Main Graphic Bank` / `GBANK_MAIN`. First-seen house/landscape files (414 / 4130 / 3172) are DXT1: framed LZO **top mip** (`PayloadBytes == TopMipSize`) then **raw leftover 43688** = mips 256..4 (`FirstSeenTextureStoresRawLowerMips`). Asking LZO for the full 1×1 chain drops the 3-byte tail (`produced = top−3`) and does not consume the leftover. Do not invent LockRect-per-level or GPU BC — sampled format after SCRATCH is unread. Format **35 skies are 16-byte blocks**; first 8 bytes `FF FF 00 00 00 00 00 00` are DXT5 alpha (`a0=a1=255`, opaque), not DXT3 nibbles. Reading them as DXT3 makes 75% of texels `alpha=0`.
4. **`CEngineSkyRenderer`** / `PSHADER_INNER_SKY`: `tex t0..t3` then `lrp` — **no `texkill`**. Discarding `t1.a < 0.08` punched the DXT3-misread alpha into horizontal stripes. `VSHADER_INNER_SKY` transforms `v0` by `c5–c8` and copies UVs from `v2`.
5. **Shader manager** ctor `00B3CB30` (global `0x1436E98`, size `0x31E0`). `PIXEL_SHADERS` is bound through a vtable on `this+10392`; the resulting manager pointer is stored at **`this+10904`**. Every `PSHADER_*` lookup is `00A5D720` on `[0x1436E98]+10904`. Vertex lookups are `00A5D5F0` on the same object: sky inner/outer use **`+10728`**, star field **`+10816`**, landscape foreground **`+10784`**.
6. **Shader bank slots** (`00B3B5D0`, push the integer then the name). Locked against `shaders.big` by `ShaderFormatTests.World_passes_are_landscape_static_water_and_sky`:

   | slot | bank |
   |---|---|
   | 5 | `SHADERS_SKY` |
   | 6 | `SHADERS_SKY_SCREEN_SPACE` |
   | 7 | `SHADERS_WATER_FOREGROUND` |
   | 8 | `SHADERS_WATER_BACKGROUND` |
   | 9 | `SHADERS_SEA_BACKGROUND` |
   | 10 | `SHADERS_WEATHER` |
   | 11 | `SHADERS_LANDSCAPE_BACKGROUND` |
   | 12 | `SHADERS_LANDSCAPE_FOREGROUND` |
   | 13 | `SHADERS_POS_COL_TEX1` |
   | 14 | `SHADERS_REPEATED_MESH` |
   | 16 | `SHADERS_POINT_SPRITE1` |
   | 17 | `SHADERS_ZSPRITE` |
   | 20 | `SHADERS_VERTEX_POS` |
   | 21 | `SHADER_SPRITE_GROUP` |
   | 22 | `SHADERS_DECAL_GROUP` |
   | 23 | `SHADERS_MESH_GROUP` |
   | 24 | `SHADERS_PARTICLE_SPRITE_TRAIL` |
   | 25 | `SHADERS_DEBUGGING` |
   | 26 | `SHADERS_TEXT` |

   A second registrar `00B3B6D0` takes `SHADERS_STATIC` (ebx), `SHADERS_STATIC_BUMP` (2), `SHADERS_PALSKIN` (3), `SHADERS_PALSKIN_BUMP` (4). ebx at that site is UNREAD.
7. **PS constants** interned just before the bank table: `PSCONST_MAX_FOG_ALPHA` default `(0.5,0.5,0.5,0.5)`, `PSCONST_TFACTOR` default `(1,1,1,1)`. Also named: `PSCONST_ZERO` / `ONE` / `HALF` / `1_0_0_0` / `0_1_0_0` / `0_0_1_0` / `0_0_0_1` / `PSCONST_SHADOW_FADE_COLOUR` plus `PSCONST_USER_0..3`, `PSCONST_OUTPUT_FACTOR`, `PSCONST_INPUT_FACTOR_0/1`.
8. **Engine components** (`Engine: Add Engine Component` @ `00B29930` appends to `this+360`). Construction order in the engine ctor includes:

   | global | ctor VA | what the listing shows |
   |---|---|---|
   | `0x1436E98` | `00B3CB30` | shader manager |
   | `0x1436E80` | `00B33B50` | intern `"MainScene"` (also builds `"RepMeshScene"`) |
   | `0x1436E60` | `00B50370` | lives next to `"EnableShadows"` |
   | `0x1436E8C` | `00B423F0` | `SetStaticMapFileForUse` |
   | `0x1436E54` | `00B73760` | water (`PSHADER_WATER_*` binds) |
   | `0x1436EA8` | `00B69000` | landscape (`PSHADER_LANDSCAPE_*` binds; pool alloc uses `[0x1436EA8]+1712`) |
   | `0x1436E50` | `00B625E0` | **`CEngineSkyRenderer`** (only caller of that ctor) |
   | `0x1436E44` | `00B52250` | weather (`EnableWeather` / `PSHADER_WEATHER_*`) |
   | `0x1436E40` | `00B5A1D0` | screen colour filter |
   | `0x1436E34` | `00B5F090` | glow |
   | `0x1436E30` | `00B5C460` | radial blur |
   | `0x1436E38` | `00B86C00` | displacement |

9. **34 render layers** (`Engine: Add Render Layer` @ `00B262C0`, 34 calls from `00B26A75`–`00B276A8`). Each layer is 28 bytes, vtable `0x12A0F04`, **bit mask at +4**, flags at +8/+12, renderer vector at **+16 / +20**. Manager vector is a normal MSVC `vector<>`: **`+348` begin, `+352` end, `+356` cap**. Attach is `00B2AC80` (push onto layer+16). Bits and attachments in registration order — and the frame walks **begin → end**, so this **is** submit order:

   | +4 bit | attached global |
   |---|---|
   | `1` | none in the listing |
   | `2` | `0x1436E60` shadows |
   | `4` | `0x1436EA8` landscape |
   | `8` | `[MainScene]+616` |
   | `0x10` | `[MainScene]+616` |
   | `0x40` | landscape |
   | `0x20` | `[MainScene]+616` |
   | `0x100` / `0x400` / `0x1000` | `[MainScene]+616` |
   | `0x2000` | **sky** `0x1436E50` |
   | `0x4000` / `0x8000` | `[MainScene]+616` |
   | `0x20000` | **water** `0x1436E54` |
   | `0x10000` | displacement `0x1436E38` |
   | `0x400000` | sky again |
   | `0x2000000` | landscape again |
   | `0x4000000` | `0x1436E3C` |
   | `0x1000000` | colour filter `0x1436E40` |
   | `0x20000000` | weather + glow + radial + displacement |
   | `0x40000000` | shader manager `0x1436E98` |
   | `0x80000000` | `0x1436E7C` |

   `[MainScene]+616` is the subobject constructed in `00B33B50` (vtable `0x12A1348`), not the sky renderer.

## Full scene pass (exe)

Traced `00B27D90` → `00B25950` → layer `00B2AB80`. Do not invent VS register numbers.

1. **Frame** `00B27D90` (only vtable site `0x012A0F5C`). Device wrapper `0x1436E18`: unbind textures (`[dev+260]` = `SetTexture(stage, 0)`), optional `00B277A0` (if flags at manager +24/+36: `00B3B4A0` on the shader manager, `00B2F740` on `0x1436E58`), then **`00B25950(this, arg)`**.
2. **`00B25950` three phases** (this in ebx). Builds a stack ctx: copies 28 dwords from `arg`, stores `this+184` at ctx+120, `this+136` and `this+248` nearby. Then:

   | phase | walk | call |
   |---|---|---|
   | 1 | components `+360…+364` | if `(this+184 & query(+40))` → **`vtbl+4`** |
   | 2 | **layers `+348…+352`** | **every layer `vtbl+4`** (`00B2AB80`) — no extra mask here |
   | 3 | components `+360…+364` | if `(this+184 & query(+40))` → **`vtbl+8`** |

3. **Layer submit `00B2AB80`** (layer this, ctx). Bail if `(ctx+120 & layer+12) == 0`. `layer+12` is 0 or 1 on construction; ctx+120 is **`manager+184`**. Then three loops over attached renderers (`layer+16…+20`):

   | loop | condition | call |
   |---|---|---|
   | prepare | `ctx+120 & renderer->query(+40)` | **`vtbl+20`** (sky/landscape/water = `ret 4` stub `00B28C60`) |
   | **draw** | same **and** `renderer+8 != 0` | **`vtbl+16`** |
   | after | same query | **`vtbl+24`** (stub `00B28C70`) |

   Base ctor `00B59710` sets **`[this+8] = 1`**, so +16 runs for sky / landscape / water / MainScene+616.
   Query (`+40`) returns **`1`** for sky (`00B66DE0`), landscape (`00B6CA10`), water (`00B7ED70`).

4. **`vtbl+16` switches on the layer bit (`arg+4`)**:

   | Renderer | +16 VA | bits handled |
   |---|---|---|
   | Landscape `0x12A2B54` | `00B6B0B0` | **`4`**: `00B67480`, `00B671A0`, walk static-map list `00BDC060`, `00B67510`. **`0x40`**: `00B68DA0`, `00B67480`, `00B677D0`, `0098B5E0(2)`, walk list `00BDC2D0`. Other bits: profiler only. |
   | Sky `0x12A293C` | `00B662F0` | **`0x400000`** → `00B64550`. Else uses `0x1436EA0+20` and continues (outer/inner/stars). |
   | Water `0x12A3364` | `00B783F0` | Ctor zeros +508..+624. Draw ORs empty vectors then `je 00B7A865`. First-seen empty. |
   | `[MainScene]+616` `0x12A1348` | `00B33010` | `lea ebx, [this-616]` then switch on the bit: **`2`** uses shadow global `0x1436E60`; **`0x20`** → `00B32610`; compares `0x400` / `0x80`. |

5. **Landscape shared setup `00B67480`** (both bit 4 and 0x40): `00B46C80` + `00B46890` on **`0x1436E9C`** (the 0x46D0 object from ctor `00B482A0`), then `009881F0(0x1436E14, matrix)` with **1.0f at +4, +20, +36** (identity-like 4×4 cached on the device wrapper at +496…+556, last `w=1`). Wrapper `00989A60` is `SetVertexShaderConstantF`; lights are `c19`/`c20`, MAD addend is `c35=(0,0,0,1)`, landscape UV writes `c3`.
6. **Patch submit** `00BDC2D0` / `00BDC060`: require `[patch+8]` and `[patch+4]`, touch `0x1436EA0+0x1C8`, then x87 (`fld`). Exact DrawIndexed path UNREAD.
7. **Pre-pass `00B277A0`**: `00B3B4A0(shader manager)` releases caches at +2288/+2296/+2304 and calls `00BD9B50([0x1436EA8]+1712)` — same pool object as `EnablePoolAllocation`.
8. **`CEngineStateBlockDiffuse2X` apply** — still RTTI only. `0098B5E0(n)` is a device-wrapper call used with **2** (landscape 0x40) and **3** (earlier in `00B25882`).
9. **Client walk.** `ScenePasses.Registration` is the 34-layer table. We submit landscape on bits **4** (1-tex `PSHADER_LANDSCAPE_BACKGROUND` shape) and **0x40** (proven `mul_x2` FG), static meshes once on **0x20** (first MainScene+616 bit after FG; other +616 bits unread), sky else-path on **0x2000**. Water / shadows / `0x400000` sky stay out. Locked by `ScenePassTests`.

10. **Per-renderer shader stores**
    - Sky: `PSHADER_INNER_SKY` / `_SIMPLE` → `this+292`; `PSHADER_OUTER_SKY` → `+300`; `PSHADER_SKY_STAR_FIELD` → `+260`; `VSHADER_OUTER_SKY` → `+244`; `VSHADER_SKY_STAR_FIELD` → `+252`.
    - Landscape: `PSHADER_LANDSCAPE_FOREGROUND` → `this+1388`. VS family is `VSHADER_LANDSCAPE_FOREGROUND` plus `_2LIGHTS` / `_4LIGHTS` / `_5LIGHTS` × bump / spot / shadow / colbuff, and `_BLACKOUT_PASS`.
    - Water: `PSHADER_WATER_SKY_MAP` → `+452`; `PSHADER_WATER_ENVIRONMENT_MAP` → `+436`.
11. **`EnableSky` / `EnableLandscape` / `EnableWater` / `EnableWeather` / `EnableShadows` / `EnablePrimitives`** are `ret 4` name interners, not the draw path.
12. **State blocks** exist as RTTI only in this dump: `CEngineStateBlockSolid`, `Solid2X`, `Alpha`, `Alpha2X`, `AdditiveAlpha`, `AddSmoothAlpha`, `Alpha2XTFactor`, `DiffuseOnly`, **`Diffuse2X`**, `DiffuseEnv2X`.
13. **`CEngineLandscapeMeshBuilder` / `CPatchTesselationEdgeStrip`**: 17×17 (`v=289`, flag 256) is a full grid; other tiles use PrimitiveCount+2 strip indices plus stored edge-strip objects.

## Exe dump tool

`tools/Fable.ExeIndex` indexes a local `Fable.exe` (`index` → `split` → `translate` packets). Each family is `out/01-sections/<family>/INDEX.md` plus one file per VA — not one giant markdown. `out/manifest.json` records exe identity + recipe version; a family is rewritten only when the exe or that version changes (`--force` overrides). Opcode searches (`calldisp`, shader tokens, bind sites) belong in `out/` as a dump family (`trace-shaders` writes `shader-tokens/`), not as one-off greps. Dumps stay gitignored. Do not commit strings, disasm, or the AI packets. After a dump, translate each `out/02-translate/*.prompt.md` into `out/03-pseudo/*.md`.

## How to add a note

- Prove a positive with a test against the live install.
- Prove a negative the same way (`Assert.True(inRange < threshold, "unexpectedly …")`).
- One sentence in this file pointing at the test.
- Do not check in game bytes.
