# ❔ The keyframe interpolator is not found — but four routes are now excluded **Status: ❔ undecodable so far, with reach.** 2026-09-02. Instrument: ⟨image⟩ — mechanical scans of `/image/sylpheed.pe`, no database rows. Question 1 of [`../agents/PLAYTEST-2026-09-02.md`](../agents/PLAYTEST-2026-09-02.md) has two halves. **The behavioural half is answered** — the game interpolates piecewise-linearly, every frame, and the declared keyframes predict the measured per-frame steps ([`splash-interpolates-every-frame.md`](splash-interpolates-every-frame.md)). **This page is the other half: *which function*, and it is not found.** Recorded because a bounded negative stops the next attempt repeating four searches that cost nothing to state and an hour to redo. --- ## What was excluded, and how The UI region throughout is `0x82200000 … 0x823FFFFF`, which is where the corpus places the screen/bundle subsystem and the quad-emitter chain ([`ui-quad-class-foothold.md`](ui-quad-class-foothold.md)). | route | search | result | |---|---|---| | **integer lerp** | a `divw`/`divwu` with both a `mullw` and a `subf` in the preceding 10 instructions — the shape of `v0 + (v1−v0)·(t−t0)/(t1−t0)` | **28 sites image-wide, NONE in the UI region.** The evaluator is not integer multiply-divide there | | **indexed record access** | `mulli rD,rA,40` — the keyframe record stride is 40 bytes ⟨disc⟩ | **31 sites, NONE in the UI region.** Records are not addressed by multiply | | **pointer walk by stride** | `addi rX,rX,40` inside the UI region | 43 sites. The ones in the screen/bundle subsystem — `0x823CCA6C`, `0x823CCA94` — disassemble to a **40-byte container copy loop** (`mtspr CTR,10` then a 10-word copy), i.e. a `vector<40-byte>` reallocation. **Not an evaluator** | | **float lerp** | `fmadd`-family ops in the UI region | **811 sites over 142 pages** — far too diffuse to select on. Not excluded; **not narrowed either** | 📌 **The useful part is the first two.** Together they say the evaluator does not compute a fraction with integer multiply-and-divide, and does not index its records by multiplication. Combined with the measured behaviour — steps that land on exact integers like −3 and −14 — the likeliest remaining shape is a **float lerp with a reciprocal computed once per segment**, then converted to a byte. That is a hypothesis, and the `fmadd` scan shows it cannot be found by opcode shape alone. ## What would find it, in cost order 1. **Trace down from the quad emitter.** `sub_823C2AC0` — the accessor that hands out the vertex pointer — has exactly **6 callers**, four of them sibling quad emitters (`sub_821D6A40`, `sub_822380B0`, `sub_82234610`, `sub_821BC718`). `sub_822380B0` calls it and then reads its colour from fields of `this` (`+0x04`, `+0x08`, `+0x0C`, `+0x10`). **Whoever writes those fields is one step from the interpolator**, and the caller set is small enough to enumerate. This is the route I would take next and did not have budget for. 2. **Watch it from the guest instead of reading for it.** The alpha is in a vertex buffer at a known address each frame; a watchpoint on the byte that becomes `k_8_8_8_8` alpha would name the writer directly. `/canary` is read-write and already carries four RE logger patches. ## Why this is not blocking the port **It is not.** The port needs to know *whether to lerp and how*, and that is answered and measured. Naming the function would let the answer generalise from the splashes to every screen without further captures — worth having, not worth holding the port for. ## Reach ⟨image⟩, exhaustive over the whole 9.2 MB for the two excluded opcode shapes, so those negatives are complete rather than sampled — the instruction forms searched genuinely do not occur in that address range. **They do not exclude the interpolator living outside `0x82200000…0x823FFFFF`**, which is an assumption inherited from `ui-quad-class-foothold.md` and not independently checked here. ## Increment 2026-09-02 — route 1 walked one step, and `sub_822380B0` is identified I took the route this page named — trace **down** from the quad emitter — and got one step, not to the interpolator. `sub_822380B0` disassembles as a **type-tagged draw-queue walker**, not a geometry emitter: ``` 822380CC bl 0x823C2AC0 ; get the vertex pointer 822380D0 lwz r11,12(r26) ; head 822380D4 lwz r10,16(r26) ; count -> r17 = head + count 822380E0 stw r3,32(r26) ; stash the vertex pointer in the object 82238104 subf r11,r25,r17 ; loop bound 82238134 lwz r10,4(r26) ; the ITEM ARRAY base 82238138 rlwinm r9,r11,2,0,29 ; index * 4 -> an array of POINTERS 82238140 lwzx r29,r9,r10 ; item = array[i] 82238148 lwz r11,8(r29) ; item TYPE TAG 8223814C cmpi cr6,r11,2 ; dispatch on it ``` **So the object at `r26` is a queue** — `+4` item-pointer array, `+8`/`+0x0C`/`+0x10` bounds and indices, `+0x20` the vertex write pointer — and the function **consumes** it, switching on a per-item tag at `item+8`. 📌 **That is a partial answer to a different open question.** [`ui-quad-class-foothold.md`](ui-quad-class-foothold.md) records *"no function that iterates screen elements has been found"*. This iterates a list of UI items and dispatches per item. It is a **draw queue**, not a screen's declared element list, so it is not that walk — but it is the consumer immediately downstream of it, and the type tag at `item+8` is a concrete field to chase. 🔴 **It is still not the interpolator.** Alpha is already decided by the time an item reaches this queue: the emitter reads what it needs from the item and writes vertices. **The evaluator runs before the enqueue**, so the next step is whoever *pushes* items — the writer of `+0x0C`/`+0x10` on this object. ⚠️ Recorded as an increment rather than a finding: one function identified by disassembly, no behaviour measured, and the identification rests on the shape of the code alone.