Files
Sylpheed/docs/re/ui-quad-class-foothold.md
Sylpheed RE agent 5d5680a0d5 docs/re: more UI map, no walk — and say that this is becoming a rabbit hole
Three facts, none of them the thing being looked for:

* the drawing API is enumerable — the renderer accessor the quad emitter uses has
  exactly 6 callers, four of them sibling quad emitters;
* sub_823C2990 is a FACTORY, not the singleton accessor its use in the top-level
  render function suggested: it allocates 4 bytes plus a 244-byte object from the
  heap at [0x828E2B14] and runs the chain that ends at the constructor installing
  vtable 0x820b30b4. So a UI item is 244 bytes;
* the 0x823C region holds both the item class and four of the RATC-fourcc
  loaders, so bundle parsing and item construction live together.

And the part worth writing down more than any of them: three iterations have
added map without answering the question, and each step has been a plausible next
query rather than a decisive test — the shape of a search that can run forever.
The decisive alternative is costed instead of started: a Canary memory watch on
the UI vertex buffers would report the guest PC that writes them, naming the
emitter and its caller outright. That is real emulator work, and it is now a
choice to weigh against leaving the paint order unresolved, which costs the port
one screen's fidelity and nothing else.
2026-08-19 01:23:12 +00:00

5.9 KiB

The guest's UI quad class — a foothold found from the capture's vertex layout

Status: 🟡 PROBABLE for the identification below (it is a static read, but the layout it matches was measured first). the element walk that decides paint order is not in this note — that is what the search was for, and it was not reached.

How it was found

The runtime capture says exactly what a UI vertex looks like: prim = 13 (quad list), stride 24, attributes 57@0 (k_32_32_32_FLOAT position), 6@12 (k_8_8_8_8 colour), 37@16 (k_32_32_FLOAT UV). That is a fingerprint a store sequence in the guest must match, so the binary was searched for functions that write floats at +0/+4/+8, a word at +12, floats at +16/+20, and advance a pointer by 24 (sylpheed.db, 15 candidates ranked by how many of each they contain).

The tightest match is sub_82250138 (288 B, three of each store, two addi …, 24), and it is unambiguous:

82250150  addi r5, r0, 13        ; primitive type 13 = QUAD LIST
82250154  addi r4, r0, 4         ; 4 vertices
82250158  lwz  r11, 0(r31) …     ; renderer->slot0(4, 13)   -> allocate
82250170  lwz  r11, 8(r11) …     ; renderer->slot2()        -> vertex pointer
82250188  addi r10, r0, -1       ; colour = 0xFFFFFFFF
82250194  stfs f13, 0(r3)        ; x
82250198  stw  r10, 12(r3)       ; colour   ← +12, as the capture says
822501A0  stfs f13, 4(r3)        ; y
822501A4  addi r11, r3, 24       ; next vertex ← stride 24, as the capture says
822501AC  stfs f0,  8(r3)        ; z
822501B0  stfs f0, 20(r3)        ; v
822501B4  stfs f0, 16(r3)        ; u

prim = 13, four vertices, colour 0xFFFFFFFF and stride 24 all match what the title screen was measured drawing.

What it is (and what it is not)

Its only caller, sub_8224FB78 (752 B), is a constructor: it stores a vtable pointer into 0(r29), zeroes a run of fields, sets a colour, and has the emitter lay down a default quad. So sub_82250138 builds an object's initial geometry rather than drawing a frame.

The vtable it installs is 0x820A7264 — class ANON_Class_AAFDBF89, 2 slots (sub_8224FF98, sub_8224FB18). That is the UI quad/sprite class.

It is not the element walk. Following the callers upward:

sub_82250138  (quad emitter)
  └ sub_8224FB78  (constructor, installs vtable 0x820A7264)
      └ sub_82222E70 (2 260 B)
          └ sub_821A5F10 (756 B)
              └ sub_821A8578 (3 120 B)   ← a top-level render function
                  └ sub_821A8428 (284 B)

sub_821A8578 is not data-driven: it is an unrolled run of about thirty identical bl 8217FA08 / bl 821AC450 / bl 82454918 triplets. So it sequences a fixed list of subsystems, not a list of screen elements. The element order this project is chasing is somewhere else — most likely behind one of those three repeated calls, or behind the sprite class's own two vtable slots.

Why this is worth keeping

Two reasons, neither of which is "it looks right":

  • the identification is anchored to a measurement — the vertex layout came from the running game first, and the search was for code that matches it;
  • it names a concrete class (0x820A7264) and a concrete API shape (allocate(count, prim) then get vertex pointer), which is the thing to instrument or trace next.

Not settled: everything the search was actually for. No function that iterates screen elements has been found, and nothing here bears on the paint order yet.

Second pass: the drawing API is small, and a UI item is 244 bytes

Continuing the search for the element walk (2026-08-19). Three more facts, none of which is the walk:

  • The drawing API is enumerable. The renderer accessor sub_823C2AC0 — the one the quad emitter calls to get its vertex pointer — has exactly 6 callers, and four of them (sub_821D6A40, sub_822380B0, sub_82234610, sub_821BC718, ~160 instructions each) are sibling quad emitters. So the number of places in the whole title that can emit a UI quad is small enough to enumerate, which is worth knowing before instrumenting anything.

  • sub_823C2990 is a factory, not a singleton accessor — a correction to what its use inside the top-level render function suggested. It allocates twice from a heap held at [0x828E2B14] via sub_82150EF8: 4 bytes for a handle, then 244 bytes for the object, and runs sub_823DE0C0, which is the head of the chain that ends in sub_823CB2A0 — the constructor that installs vtable 0x820b30b4. So a UI item object is 244 bytes, and the "LOGO item" class named in the emulator-era notes is what this factory builds.

  • The 0x823C… region is the screen/bundle subsystem. It contains both the item class (constructors at 823CB2A0/823CB558/823CBB90, methods 823CE558…823CFD90) and four of the functions that load the RATC fourcc (823CAF10, 823CABE0, 823CB1F0, 823CBEE8), so bundle parsing and item construction live together.

Stated plainly: this is becoming a rabbit hole

Three iterations have added map without answering the question. The searches keep landing on construction and emission, never on the per-frame walk that orders elements — and each step is a plausible next query rather than a decisive test, which is the shape of a search that can run indefinitely.

The decisive alternative, costed rather than started: the vertex buffers the capture already records (vb=0x14D10B90, …) are written by the guest CPU just before the draw. A Canary memory watch on that range would report the guest PC doing the writing, which names the emitter and its caller directly instead of inferring them. That is a real change to the emulator (the watch machinery exists for shared-memory invalidation, not for reporting PCs), so it is the next thing to weigh — against simply leaving the paint order unresolved, which costs the port one screen's fidelity and nothing else.