docs: withdraw yesterday's "paint order is a sequence" -- wrong source

Last iteration I claimed the splash's measured_paint_order [0,2,4,6,1,3,5]
records, between its glow and logo halves, the temporal order they were
seen in rather than depth -- because the halves never share a frame.

The no-overlap measurement is right (glows f94-115, logos f116-211). The
inference is wrong, on two independent grounds:

  * Wrong source. That vector is not a read of the draw capture. It is a
    read of the live screen object's CHILD ARRAY -- ui-screen-runtime.md
    records it literally as "paint order (child slots)". A child list has
    a definite order whether or not its children are ever drawn together,
    so co-occurrence does not bear on it. The capture was the CHECK.
  * The order is in the file anyway. paint_order_audit on GP_TITLE entry
    11: derived == measured, 0 inverted pairs, 0 same-layer-key ties. The
    glows and logos carry distinct T8aD keys (0xa100 < 0xa110), so the
    file orders the halves statically, no capture involved.

I asked the question that started this iteration -- do the title and menu
orders have the same problem -- and the answer is that none of the three
does, for the same reason.

What survives is narrower and now recorded with numbers: how much of each
order its capture actually cross-checks. The title capture is stable (8
draws / 12 quads / 5 textures, identical in all five captured frames
across two logs) and confirms 7 of 24 positions; the menu capture is not
(texture 0x11C30000 present in frame 0, gone by frame 3); the splash
capture cannot cross-check its middle at all.

A counting trap worth the tool: count QUADS, not draws. The menu's draw 9
is indices=24 -- six quads batched from one texture. Counting draws reads
9 where 16 are on screen, and an earlier pass of this analysis briefly
"found" three quads for six declarations that way and concluded elements
were missing. They were batched.

METHOD: check what a "measured" value was measured FROM before reasoning
about its limits. The co-occurrence rule is real, and it is specific to
orders read from draw captures.
This commit is contained in:
Sylpheed RE agent
2026-08-29 05:19:44 +00:00
parent 2532c056be
commit 7a4e4333f4
7 changed files with 171 additions and 33 deletions

View File

@@ -393,12 +393,18 @@ authored version can be deleted.
`a=255`). That is why a splash render needs `--black` rather than the default `a=255`). That is why a splash render needs `--black` rather than the default
backdrop. ⚠️ Not a general rule: the main menu's measured order puts one `.prm` backdrop. ⚠️ Not a general rule: the main menu's measured order puts one `.prm`
at position 4 and the other **last** (the transition fade). at position 4 and the other **last** (the transition fade).
⚠️ **And the splash's recorded paint order `[0,2,4,6,1,3,5]` is a temporal **The splash's recorded paint order `[0,2,4,6,1,3,5]` is a real depth
sequence, not a depth order.** Its glows and logos never share a frame, so order** — read off the runtime child array, and independently backed by the
their relative depth was never observable — the vector records the order they static layer key (glows `0xa100` sort before logos `0xa110`;
were *seen in*. Element 0 (the `.prm`) is a genuine depth observation because it `paint_order_audit` reports derived == measured, 0 ties). A same-day claim on
co-occurs with everything. **Do not read the rest as depth and composite all this page that it was "a temporal sequence, not depth" was **withdrawn**; see
seven at once** — that is precisely what fails to reproduce the screen. the ❌ section in the linked page for why, if you read it before the fix.
⚠️ Separately and still true — and it is an **activation** fact, not a
paint-order one: the splash's glows and logos are never on screen together
(glows f94115, logos f116211,
[timeline](../re/data/splash-phase-timeline.txt)). Compositing all seven at
once reproduces no frame of the real screen. Depth says *what covers what*;
it does not say *what is up*.
[`structures/ui-prm-primitives.md`](../re/structures/ui-prm-primitives.md) [`structures/ui-prm-primitives.md`](../re/structures/ui-prm-primitives.md)
* **Menu order is geometric.** Buttons sorted top-to-bottom by resting Y. This is * **Menu order is geometric.** Buttons sorted top-to-bottom by resting Y. This is

View File

@@ -826,11 +826,15 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the
four-entry enumeration showed none exists, and the question closed statically. four-entry enumeration showed none exists, and the question closed statically.
A hypothesis that predicts an artefact can be killed by looking for the A hypothesis that predicts an artefact can be killed by looking for the
artefact, which is often far cheaper than measuring the behaviour. artefact, which is often far cheaper than measuring the behaviour.
* **Two things that never co-occur have no observable relative order.** A * **Check what a "measured" value was measured *from* before reasoning about
"measured paint order" recorded from a running game looks like depth its limits.** Two things that never co-occur have no observable relative order
information throughout, but between elements that never share a frame it can — true, and a real limit on any order read from a *draw capture*. I applied it
only be the sequence they were *seen in*. The splash's vector mixes both kinds to the splash's paint order and withdrew it hours later: that vector is a read
— element 0 is real depth (it co-occurs with everything), the glow/logo split of the runtime **child array**, which has a definite order whether or not its
is not — and the difference matters, because reading it as depth invites children are ever drawn together, and the two halves in question also carry
compositing every element at once. When recording an order, note which pairs distinct static layer keys. Both the provenance and an independent static field
actually appeared together. said the order was real. The general trap is not the co-occurrence rule, it is
applying a source-specific limit to a value from a different source: the word
"measured" covers several kinds of evidence, and the page that recorded it
usually says which — `ui-screen-runtime.md` said "child slots" in as many
words. Read that line before building an argument on top of it.

View File

@@ -595,9 +595,13 @@ neighbourhood, not just the line.
glows — so whichever was active, a subset of its elements was drawn at a time. glows — so whichever was active, a subset of its elements was drawn at a time.
Selective activation is reinstated on evidence. Selective activation is reinstated on evidence.
[`ui-group-start-time.md`](structures/ui-group-start-time.md) [`ui-group-start-time.md`](structures/ui-group-start-time.md)
* "the splash's `measured_paint_order` records a front-to-back depth order" → * ~~"the splash's `measured_paint_order` records a front-to-back depth order" →
**mis-typed.** Its glows and logos never share a frame (0 overlap in 235), so mis-typed; between its glow and logo halves it records only the temporal order
their relative depth was never observable — between those halves the vector they were seen in."~~ → **that refutation is itself refuted (same day).** The
records the *temporal* order they were seen in. Element 0 (`palogo_eff0.prm`) vector is a read of the runtime **child array** (`ui-screen-runtime.md`:
is a genuine depth observation, because it co-occurs with everything. "paint order (child slots)"), not of the draw capture, so co-occurrence does
[`ui-prm-primitives.md`](structures/ui-prm-primitives.md) not bear on it; and the halves carry *distinct* T8aD layer keys
(`0xa100` < `0xa110`, `paint_order_audit`: 0 same-key ties), so the file orders
them regardless. The no-overlap measurement was correct; the inference from it
was not. What survives: a capture of this screen can only cross-check the order
*within* each half. [`ui-prm-primitives.md`](structures/ui-prm-primitives.md)

View File

@@ -0,0 +1,23 @@
# How much of each recorded paint order its draw capture actually cross-checked.
# 2026-08-29, tools/re-capture/order_crosscheck_reach.py
#
# The three measured_paint_order vectors are reads of the runtime CHILD ARRAY
# (ui-screen-runtime.md: "paint order (child slots)"). These captures are the
# cross-check, not the source. Quads = indices/4: a single draw can carry
# several (the menu's draw 9 has indices=24 = 6 quads), so counting draws
# under-reads what is on screen.
== docs/re/captures/title-screen-draw-order.log
frame 1: 8 draws, 12 quads, 5 textures
frame 2: 8 draws, 12 quads, 5 textures
frame 3: 8 draws, 12 quads, 5 textures
textures in EVERY captured frame: 5 of 5
== docs/re/captures/title-draw-order-with-alpha.log
frame 1: 8 draws, 12 quads, 5 textures
frame 2: 8 draws, 12 quads, 5 textures
textures in EVERY captured frame: 5 of 5
== docs/re/captures/main-menu-draw-order.log
frame 0: 9 draws, 16 quads, 5 textures
frame 3: 7 draws, 9 quads, 4 textures
textures in EVERY captured frame: 4 of 5
absent from some frame: ['11C30000']

View File

@@ -624,3 +624,37 @@ searches produced a positive account. The bit blocks nothing: the port's screens
composite at 0.947 correlation against a capture without it. **Parked**, with the composite at 0.947 correlation against a capture without it. **Parked**, with the
search space narrowed and the attribution method (child order, not size) left search space narrowed and the attribution method (child order, not size) left
sound for whoever picks it up. sound for whoever picks it up.
## The reach of the draw-capture cross-check (2026-08-29)
The orders above are read off the **live child list**; the draw captures confirm
them. That confirmation is partial, and this is how partial — counted by
[`order_crosscheck_reach.py`](../../../tools/re-capture/order_crosscheck_reach.py),
data in [`order-crosscheck-reach.txt`](../data/order-crosscheck-reach.txt):
| screen | captured frames | draws/frame | quads/frame | order length | positions the capture confirms |
|---|---|---|---|---|---|
| title (entry 4) | 3 + 2 | 8 | 12 | 24 | **7** named in [`ui-screen-runtime.md`](ui-screen-runtime.md) |
| main menu (entry 8) | 2 | 9 / 7 | 16 / 9 | 16 | ≤ the richest frame |
| splash (11/14) | 235 | — | — | 7 | within each half only |
Three things this makes explicit, none of which changes an order:
* **Count quads, not draws.** The menu's draw 9 is `indices=24`**six** quads
batched out of one texture. A per-draw count reads 9 where 16 are on screen,
and an earlier pass of this analysis "found" three quads for six declarations
that way and briefly concluded elements were missing. They were batched.
* **The title capture is stable**: 8 draws / 12 quads / 5 textures, byte-for-byte
the same census in every one of the five captured frames across two logs. So
everything it shows co-occurs, and the 7 confirmed positions are mutually
ordered by observation. The other 17 rest on the child-list read alone — which
is the ground truth here, but it is one source, not two. (The census counts 8
draws where that page names 7 confirmed elements, so the capture carried a
little more than it was read for — not chased, and not a claim either way.)
* **The menu capture is not stable**: texture `0x11C30000` is in frame 0 and gone
by frame 3. Its order is still confirmed as far as any single frame goes.
* **The splash capture cannot cross-check its middle.** Glows (f94115) and logos
(f116211) never share a frame, so no capture of this screen orders one against
the other. The static layer key does (`0xa100` < `0xa110`), which is why the
order stands — see the ❌ withdrawn section in
[`ui-prm-primitives.md`](ui-prm-primitives.md).

View File

@@ -209,19 +209,39 @@ splash renders need `--black` to match.
position 4 and `pteff00.prm` **last** — that one is the screen-transition fade. position 4 and `pteff00.prm` **last** — that one is the screen-transition fade.
A `.prm` paints where its screen's order says; the splash's happens to be first. A `.prm` paints where its screen's order says; the splash's happens to be first.
## ⚠️ And the splash's "measured paint order" is a SEQUENCE, not a depth order ## ❌ WITHDRAWN (2026-08-29, same day): "the splash's measured order is a sequence, not depth"
`measured_paint_order` returns `[0, 2, 4, 6, 1, 3, 5]` for the splash, commented This page briefly claimed that `measured_paint_order`'s `[0, 2, 4, 6, 1, 3, 5]`
*"the full-screen `.prm`, then all three glows, then the three logos"*. records, between its glow half and its logo half, the *temporal* order they were
seen in rather than a depth order — on the grounds that the two halves never
share a frame (0 overlap in 235, which is
[measured and still true](../data/splash-phase-timeline.txt): glows f94115,
logos f116211).
The draw capture shows the glows and the logos **never appear in the same frame** **The measurement was right and the conclusion was wrong**, on two independent
— 0 overlapping frames in 235 ([group start time](ui-group-start-time.md)). Two grounds, either of which is fatal:
elements that never co-occur have **no observable relative depth**. What that
vector records between its glow half and its logo half is the *temporal* order
they were seen in, not a front-to-back relationship.
🟡 This does not make the render wrong — compositing them in that order is * **Wrong source.** The vector was never read off the draw capture. It is a read
harmless, and the ✅ for element 0 (the `.prm` first) *is* a real depth of the live screen object's **child array**
observation, since it co-occurs with everything. But the type of the claim [`ui-screen-runtime.md`](ui-screen-runtime.md) records it literally as
matters for a consumer: **treating the vector as depth invites compositing all `paint order (child slots): 0 2 4 6 1 3 5`. A child list has a definite order
seven elements at once, which is exactly what does not reproduce the screen.** whether or not any two of its children are ever drawn together, so a
co-occurrence argument cannot touch it. The capture was the *cross-check*, not
the source. That page had already stated the limit honestly for the title
build: the `kind = 0x4` instances "draw at α=0 and so never appeared in the
capture, but their placement in the list is consistent with the grouping".
* **The order is in the file anyway.** `examples/paint_order_audit` on
`GP_TITLE.pak` entry 11: `derived == measured : YES`, `inverted pairs: 0`,
**`same-layer-key ties: 0`**. The glows and the logos carry *distinct* T8aD
layer keys (`0xa100` vs `0xa110` — [key](ui-paint-order-key.md)), so the glow
half sorts before the logo half **statically**, with no capture involved. The
relationship I called unobservable is decoded.
What survives is narrower and worth keeping: **a draw capture of this screen
cannot cross-check the glow-vs-logo half of the order**, because the two halves
never co-occur. The capture validates the order *within* each half only. So the
line in [`ui-paint-order-key.md`](ui-paint-order-key.md) — "read off the live
child list, checked against a draw capture" — has that reach limit on the splash.
The order itself is not in doubt; the *capture's* power to confirm all of it is.
The ✅ above for element 0 is unaffected: it is a direct draw observation.

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""How much of a recorded paint order did its draw capture actually cross-check?
The three `measured_paint_order` vectors in ui_layout.rs are reads of the live
screen object's CHILD ARRAY (ui-screen-runtime.md: "paint order (child slots)").
The draw captures are the cross-check, not the source -- so they bound how much
of each vector was independently confirmed, nothing more.
An element that is never drawn in the captured window (alpha 0, or its phase
never ran) contributes no confirmation. This counts what each capture could see:
draws, quads (indices/4 -- batched draws carry several), and distinct textures
per frame, plus which textures fail to appear in every frame.
"""
import re
import sys
import collections
def census(path):
frame, per, draws, tex = 0, collections.Counter(), collections.Counter(), collections.defaultdict(set)
for ln in open(path):
if ln.startswith('--- frame'):
frame = int(re.search(r'frame (\d+)', ln).group(1))
continue
m = re.match(r'\s*(\d+) prim=(\d+) indices=(\d+)', ln)
if not m or int(m.group(2)) != 13:
continue
per[frame] += int(m.group(3)) // 4
draws[frame] += 1
t = re.search(r'base=0x([0-9A-F]+)', ln)
tex[frame].add(t.group(1) if t else 'untextured')
return per, draws, tex
for path in sys.argv[1:]:
print(f'== {path}')
per, draws, tex = census(path)
if not per:
print(' no prim=13 draws')
continue
for f in sorted(per):
print(f' frame {f}: {draws[f]} draws, {per[f]} quads, {len(tex[f])} textures')
sets = [tex[f] for f in sorted(tex)]
common, allt = set.intersection(*sets), set.union(*sets)
print(f' textures in EVERY captured frame: {len(common)} of {len(allt)}')
if allt - common:
print(f' absent from some frame: {sorted(allt - common)}')