diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 5db7370..54172a0 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -6941,3 +6941,28 @@ a structural difference, not a coincidence. 🔴 **Blocked**: the `+184` writer. Next route with power = the RTTI behind those two anonymous classes, or a runtime watch — **not** another static offset search. + +--- + +## ✅ 2026-08-28 — the `ind_call` damage is bounded, and no corpus claim was contaminated + +Item (a), the protective sweep after last iteration's refutation. + +✅ **Every caller-count claim in the corpus verifies against `call`, with zero +`ind_call`**: `sub_82457780` "one caller" → 1; `sub_8226EAB8` "16 callers" → 16; +`sub_822737C8` "seven callers" → 7; `0x82175110` "22 callers" → 22. **4 of 4 +exact, 0 `ind_call` edges among them.** + +🔑 **And there is a structural reason, not luck.** Targets with `ind_call` edges: +**1 710**; with `call` edges: **12 170**; **both: 103**; `ind_call` only: +**1 607**; `call` only: 12 067. **All 1 710 `ind_call` targets are vtable +entries — 1 710 of 1 710, zero partials** (control: only 273 of the 12 170 +`call` targets are). So `ind_call` fires *only* on virtual methods, and 1 607 of +them have no direct caller at all — exactly why the two `EX_` screens were empty +on `call`. + +⚠️ **Residual risk, named**: the **103** targets with *both* kinds, where an +unfiltered count would inflate by ~633. Elsewhere the populations are disjoint. + +**Rule**: always filter `kind='call'`; and a function with no `call` edges is a +**virtual method**, not a gap to fill from `ind_call`. diff --git a/docs/re/structures/result-screens.md b/docs/re/structures/result-screens.md index fd85265..25d3197 100644 --- a/docs/re/structures/result-screens.md +++ b/docs/re/structures/result-screens.md @@ -184,3 +184,46 @@ structural difference rather than a coincidence. graph are exhausted for it. A route with actual power would be the RTTI class behind `ANON_Class_271D5F25` / `_CAA8AD62`, or a runtime watch on the field — not another static offset search. + +### ✅ How far the `ind_call` damage reaches — bounded, and the corpus is clean + +The refutation above is corpus-wide in principle, so the next question is which +existing claims it voids. Answer: **none of the ones the corpus makes.** + +**Every caller-count claim in the corpus checks out against `call` edges, with +zero `ind_call` contamination:** + +| doc | claim | `call` | `ind_call` | +|---|---|---|---| +| `title-crash-stl-tree` | `sub_82457780` "one caller" | **1** | 0 | +| `isl-trigger-queue` | `sub_8226EAB8` "16 callers" | **16** | 0 | +| `isl-coroutine-spawner` | `sub_822737C8` "seven callers" | **7** | 0 | +| `challenge-mission-gate` | `0x82175110` "22 callers" | **22** | 0 | + +4 of 4 exact, and none of the four has a single `ind_call` edge. + +**And there is a structural reason, not luck.** Partitioning the two target +populations: + +| | count | +|---|---| +| targets with `ind_call` edges | 1 710 | +| targets with `call` edges | 12 170 | +| **both** | 103 | +| `ind_call` only | 1 607 | +| `call` only | 12 067 | + +🔑 **All 1 710 `ind_call` targets are vtable entries — 1 710 of 1 710, zero +partials.** (Against a control: only 273 of the 12 170 `call` targets are.) So +`ind_call` fires *only* on virtual methods, and **1 607 of them have no direct +caller at all** — which is exactly why the two `EX_` screens came back empty on +`call` and flooded on `ind_call`. + +⚠️ **The residual risk, named precisely:** the **103** targets carrying *both* +kinds. There a caller count taken without a `kind` filter would be inflated by +~633. Everywhere else the two populations are disjoint, so a `call`-based count +is safe and an `ind_call`-based one is meaningless. + +**Rule for future work:** always filter `kind='call'`; and when a function has no +`call` edges, that is a *fact about it* — it is a virtual method — not a gap to +be filled from `ind_call`.