Walked the route the bounded-negative page named -- trace down from the quad emitter -- and got one step. sub_822380B0 is not a geometry emitter. It fetches a vertex pointer from sub_823C2AC0, stashes it at this+0x20, then loops over an array of item POINTERS at this+4 bounded by this+0x0C and this+0x10, loading a type tag from item+8 and dispatching on it. So the object is a draw QUEUE and this function consumes it. That is a partial answer to a different open question: ui-quad-class-foothold.md records that no function iterating screen elements has been found. This iterates UI items and dispatches per item. It is a draw queue rather than a screen's declared element list, so it is not that walk, but it is the consumer immediately downstream and the type tag at item+8 is a concrete field to chase. Still not the interpolator: alpha is already decided by the time an item reaches the queue. 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, not a finding: one function identified by disassembly, no behaviour measured, identification resting on code shape alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
108 lines
5.9 KiB
Markdown
108 lines
5.9 KiB
Markdown
# ❔ 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.
|