Files
Sylpheed/docs/re/structures/ui-screen-runtime.md
Sylpheed RE agent 875ac2632d docs/re: two ground-truth paint permutations, and four more refutations
Deriving the child order from the bundle is what the port needs, so this collects
data rather than guessing. Both multi-element screen objects live on the title:

* a 7-element bundle paints 0 2 4 6 1 3 5 — evens then odds, a stable partition;
* the 24-element title build paints 9 11 12 10 13 6 20 19 14 15 18 16 17 0 2 4 7
  1 3 5 22 23 21 8.

Two things the pair establishes. The list is GROUPED, not shuffled: elements
sharing a sprite are contiguous (ptlogo1's three instances at slots 13-15,
ptlogo2's at 17-19, the back2eff family at 8-12). And the five single-keyframe
elements lead — exactly elements 9,10,11,12,13 have one keyframe, and the list
opens with all five, which is a file-visible property.

Refuted on this data: first-keyframe time and resting time (back2eff1 starts at
52 and rests at 56, yet paints after back2 at 66/80), declaration order and its
reverse, and the RATC child order (ptbase2 is child 4 and paints first while
ptlogo1 is child 0 and paints fourteenth).

Not settled, and said so: the rule. The group CONTENTS are explained; the group
ORDER is not, and two permutations are too few to build it.
2026-08-19 01:56:04 +00:00

8.7 KiB
Raw Blame History

The UI screen object at runtime — found in live guest memory

Status: CONFIRMED for the object's identity and its element array (five independent field matches against the file, on a running game). 🟡 the remaining arrays are unidentified. nothing here settles the paint order — see the "what this does not answer" section, which is the reason the search happened.

How it was found, in one step

The item class from the emulator-era splash work has vtable 0x820b30b4, and sub_823C2990 allocates its objects at 244 bytes each (ui-quad-class-foothold.md). A vtable pointer is a fixed 4-byte value at offset 0 of every instance, so the objects are findable in the live guest image without a debugger:

tools/re-capture/gmem.py find hex:820b30b4      # while the title screen is up

Seven live objects, at 0xBCD24D88, 0xBCD25188, 0xBCD25388, 0xBCD25488, 0xBCD25588, 0xBCD25688, 0xBCD26488 — spaced 0x100 apart, consistent with a 244-byte object plus allocator overhead.

What they are

Each object begins with two vtable pointers (0x820b30b4, 0x820b31a4 — multiple inheritance) and then a run of {pointer, count, capacity} triplets. The counts identify the objects immediately:

object first triplet's count which GP_TITLE build
0xBCD25188 7 builds 0/1 — the loading overlay (7 elements)
0xBCD25388 24 build 4 — the title (24 elements)
0xBCD24D88 1 builds 2/3 — the PRESS Ⓐ BUTTON bundle (1 element)

So the three bundles this project decoded statically are all resident, with the element counts the files declare — including the two-bundle composition of the title screen that the draw capture inferred.

The element array

+0x08 of the screen object is {ptr, count, capacity} pointing at an array of 48-byte entries, one per element, in declaration order. Verified field-by-file at five positions on the title build, with no misses:

index file says (build 4) live memory at +0x10/+0x14
0 ptlogo1.t32 pivot (451,50) 451.0, 50.0
1 ptlogo2.t32 pivot (449,46) 449.0, 46.0
6 pteff01.t32 pivot (320,160) 320.0, 160.0
9 ptbase2.t32 pivot (320,180) 320.0, 180.0
21 ptcopyright.t32 pivot (309,10) 309.0, 10.0

Entry layout as far as it is read:

+0x00  u32   pointer (per-element data)
+0x04  u32   0
+0x08  u32   flags        (0x10081021 on element 0)
+0x0C  u32   ?
+0x10  f32   pivot X      ← the declaration entry's pivot, as a FLOAT
+0x14  f32   pivot Y
+0x18  u32   0
+0x1C  u32   ?
+0x20  u32   pointer  }
+0x24  u32   count    }   the element's KEYFRAMES — 8/8 on elements 0 and 1,
+0x28  u32   capacity }   which is exactly their keyframe count in the file
+0x2C  u32   ?

So the pivot and the keyframe group, which this project reads out of the bundle, are present at runtime in the same order and with the same values.

What this does not answer

The paint order. The array is in declaration order, and the capture proves the screen is not painted in that order, so the renderer either walks something else or sorts. Three more {ptr,count,capacity} triplets are present in the screen object and unidentified — +0x14 (24 entries of 12 bytes, each {ptr,1,1}, pointing into a densely packed region), +0x24 (2 entries) and +0x30 (24). The +0x14 one is not an index list (its entries are pointers, not small integers), so the obvious "draw order table" reading is already out.

What is better than before: the question is now a data question on a live structure that can be dumped in seconds, rather than a code-reading exercise. The next probe is to dump +0x24 and +0x30 on the title build and see whether either is 24 entries in a non-declaration order.

The paint order is the screen object's CHILD array at +0x30

Status: CONFIRMED. The question this project has carried since the first capture — what orders the elements, given that the declaration table does not — is answered: the screen object holds a second list, a reordering of the elements built at load time, and that list is the paint order.

+0x30 is {ptr, count, capacity} → an array of 24 pointers (not 12-byte records like +0x14). Each pointer is the +0x00 field of one of the 48-byte element records, so the two arrays hold the same objects in different orders. The permutation, read live off the title screen:

child slot element child slot element
0 9 ptbase2 12 17 back2eff4
1 11 ptloop01 13 0 ptlogo1
2 12 ptloop02 14 2 ptlogo1 (copy)
3 10 pteff04 15 4 ptlogo1 (copy)
4 13 pteff02.prm 16 7 ptlogo_tm
5 6 pteff01 17 1 ptlogo2
6 20 ptlogo_back2eff 18 3 ptlogo2 (copy)
7 19 ptlogo_back2 19 5 ptlogo2 (copy)
8 14 back2eff1 20 22 ptlogoall_eff
9 15 back2eff2 21 23 ptlogoall_eff2
10 18 back2eff5 22 21 ptcopyright
11 16 back2eff3 23 8 pteff00.prm

Checked against the capture, not merely plausible

The seven elements the draw capture can name occupy child slots

ptbase2 0   back2eff 6   back2 7   ptlogo1 13   tm 16   ptlogo2 17   copyright 22

strictly ascending, in exactly the captured submission order. Two details make this more than a coincidence of a short list:

  • it resolves the ambiguity that no static field could: slot 6 is element 20 (ptlogo_back2eff), slot 7 is element 19, so the pair paints 20-then-19 — descending in declaration terms. Nothing in the file predicts that, and the runtime list states it;
  • the three kind = 0x4 repeat instances of each logo sit immediately after their template (slots 13,14,15 and 17,18,19), which the declaration table interleaves (0,1,2,3,4,5). They draw at α=0 and so never appeared in the capture, but their placement in the list is consistent with the grouping.

What this means for the port, stated carefully

The reimplementation cannot read a runtime array — it has to derive this order from the bundle. That derivation is not solved. What the order shows is structure worth attacking: elements sharing a sprite are adjacent (ptlogo1×3 together, ptlogo2×3 together, the back2eff* family together), and the full-screen/effect elements lead. So the load-time build is doing some grouping, not an arbitrary shuffle.

Until it is derived, a reimplementation has two honest options: hard-code the captured order for the screens that have been captured, or paint in declaration order and accept that the title screen composites wrongly. The first is what the evidence supports; the second is what the viewer does today.

Two ground-truth permutations, and what they rule out

Deriving the child order from the bundle is what the port needs, so the first step is data. Both live screen objects with more than one element, read off the title screen:

A 7-element bundle (pivots (640,360) (250,36) (260,46) (120,44) (130,55) (194,68) (204,78) — three pairs 10 px apart plus a full-screen layer):

paint order (child slots): 0 2 4 6 1 3 5

— the even declaration indices in order, then the odd ones. A stable partition into two groups, nothing more.

The 24-element title build:

paint order: 9 11 12 10 13 6 20 19 14 15 18 16 17 0 2 4 7 1 3 5 22 23 21 8

What the pair shows

  • The list is grouped, not shuffled. Elements sharing a sprite are contiguous: ptlogo1's three instances (0,2,4) at slots 1315, ptlogo2's (1,3,5) at 1719, the back2eff* family at 812. The 7-element bundle's even/odd split is the same phenomenon in miniature if its evens and odds are two sprites.
  • The five single-keyframe elements lead. In the title build, exactly elements 9, 10, 11, 12, 13 have one keyframe, and the child list opens with 9, 11, 12, 10, 13 — all five, before anything animated. That is unlikely to be chance (5 of 24 in the first 5 slots), and it is a file-visible property.

Refuted as the ordering key, on this data

  • first-keyframe timeback2eff1 starts at t=52 and paints after back2 (t=66);
  • resting-keyframe time — same pair, 56 against 80;
  • declaration order and its reverse — neither, obviously;
  • the RATC child (sprite) order — the group order does not ascend in it (ptbase2 is RATC child 4 and paints first; ptlogo1 is child 0 and paints fourteenth).

Not settled: the rule. Two permutations are enough to kill the simple candidates and not enough to build the real one — the group contents are explained (same sprite), the group order is not.