re: the main menu has no idle self-return -- and four of my own durations were a backlog
Two results and one retraction, all from the same session. REFUTED: 'an ~8-10 s idle returns to the title' does not apply to the main menu. Held untouched it stayed put for >= 60 s, correlation never leaving 0.9245-0.9249. That timer is real but belongs to the TITLE. It was the only reason 'B leaves the main menu' was classed as authored, so Q5's B rule is upgraded to measured-ordering: B is delivered (canary logs vk=5801) and is the only input in >= 100 s before the return. The PRESS (A) plate: the boot title presents build 4 WITHOUT the plate first -- green-glyph 154, against 159 on the committed no-plate capture and 753/977/1493 on plate titles -- and the plate arrives after. That is the port's third option. RETRACTED: four durations taken the same day. classify_array costs 1503 ms per frame; running it per frame against an 8 fps x11grab drained the pipe at 0.64 fps, so every classified frame was stale and increasingly so. It manufactured 'plate 24.66 s after the title art', 'B->title 15.58 s', 'B->title 25.60 s' and 'A->menu 20.26 s'. The tell: a transition, a press and a fade do not share a duration, and the two B figures GREW across a longer run. A backlog preserves ordering and destroys durations, which is why the sequence results above stand and every timing does not. The ring's period is unaffected and that was checked, not assumed -- ring_period ran at 15.03 fps against a requested 15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KNR5Y79D1T4bBr6gJQaWFP
This commit is contained in:
@@ -32,7 +32,7 @@ Confidence: ✅ seen in a capture · 🟡 inferred · ❔ unknown.
|
||||
| 1 | **SQUARE ENIX** in white on black, the two dots in red, `™` after it ✅ | nothing | fades on to the next logo |
|
||||
| 2 | **GAME ARTS**, **SETA** and **studio anima** stacked on black ✅ | nothing | fades on into the cinematic |
|
||||
| 3 | The opening cinematic | **Ⓐ** skips it ✅ | ends into the title |
|
||||
| 4 | **Title screen** — the wordmark, then `PRESS Ⓐ BUTTON` below it and the 2006/2007 Square Enix copyright line at the bottom ✅ | press **Ⓐ** | goes to the main menu ✅ |
|
||||
| 4 | **Title screen** — the wordmark appears **first, with no prompt**; `PRESS Ⓐ BUTTON` fades in a beat later, above the 2006/2007 Square Enix copyright line ✅ | press **Ⓐ** | goes to the main menu ✅ |
|
||||
| 5 | **Main menu** | — | see §2 |
|
||||
|
||||
Both logo screens are **still pictures the game draws**, not video — neither is a
|
||||
@@ -62,6 +62,14 @@ predicts which ✅ — budget retries.
|
||||
Five options in a vertical stack, roughly centred, with a highlighted state on
|
||||
the focused one.
|
||||
|
||||
> ✅ **The focused option carries a small ring to the left of its label, and the
|
||||
> ring turns — continuously, about once every 2.2 s.** It has a bright head, so
|
||||
> you can see it go round. It is the **only** thing moving on this screen once it
|
||||
> has settled: the labels, the bracket and the footer are all completely still
|
||||
> (temporal std exactly 0.000 over 20 s). Ⓑ
|
||||
> [five frames, 4 s apart](../re/captures/focus-ring/ring-single-frames-4s-apart.png) ·
|
||||
> [the measurement](../re/focus-ring-spin-measured.md)
|
||||
|
||||
| position | label | what it opens |
|
||||
|---|---|---|
|
||||
| 1 | **NEW GAME** | a **DIFFICULTY** prompt, then **SELECT DATA** ✅ |
|
||||
@@ -97,11 +105,14 @@ assume the top item, and do not assume the middle one either.
|
||||
> ⚠️ **The main menu is the one screen whose footer does not offer Ⓑ.** It reads
|
||||
> `⊙ : Select Ⓐ : OK` — every submenu adds `Ⓑ : Back`. Measured: **zero**
|
||||
> red-Ⓑ glyph pixels anywhere in the frame, on two captures, with the same
|
||||
> detector finding the glyph on `EXTRAS` and `DIFFICULTY` ✅. Ⓑ *was* observed
|
||||
> returning to the title, but only once, without a capture, and the title also
|
||||
> returns on its own after ~8–10 s idle — so an observer could not have told the
|
||||
> two apart. Treat "Ⓑ leaves the main menu" as likely but unproven
|
||||
> ([the measurement](../re/menu-navigation-semantics.md#-refutation-attempt-2026-08-29--the-main-menus-own-footer-does-not-advertise-ⓑ)).
|
||||
> detector finding the glyph on `EXTRAS` and `DIFFICULTY` ✅.
|
||||
> ✅ **But Ⓑ does leave it, and the objection that stood here is refuted
|
||||
> (2026-08-29).** This page used to say the title "returns on its own after
|
||||
> ~8–10 s idle", so an observer could not tell Ⓑ from the timer. That timer
|
||||
> belongs to the **title**, not to this screen: the main menu was held untouched
|
||||
> for **≥ 60 s** and never moved. Ⓑ is delivered and is the only input in ≥ 100 s
|
||||
> before the return, so the ordering is measured — the *latency* is not
|
||||
> ([the measurement](../re/menu-idle-and-b-2026-08-29.md)).
|
||||
|
||||
*Internals: `GP_TITLE.pak` build 5; buttons `ptbtn01`–`ptbtn05` top to bottom.*
|
||||
|
||||
|
||||
@@ -49,6 +49,54 @@ a bind mount on another device, and which is what the withdrawn banner ran.
|
||||
`sylpheed-cli` against a pak, run the disc-gated tests and read the executable
|
||||
again. New measurements are available; ask for them.
|
||||
|
||||
## ✅ 2026-08-29 — three answers from one oracle session (the port's asks 1, 2 and 3)
|
||||
|
||||
* **1 — the focus ring SPINS CONTINUOUSLY. Period 2.18 s wall-clock; author it
|
||||
as 120 units = 60 frames = 2.00 s at 30 Hz.** It does not ramp once and stop.
|
||||
Measured with no angle estimated anywhere — the angular estimator written for
|
||||
this **failed its own control** (a synthetic 30° came back as 0°) and was not
|
||||
used. What settles it instead: total annulus brightness is conserved to
|
||||
**0.4 %** while individual angular bins swing by **24** — brightness moving
|
||||
*around* the ring, which excludes a pulse — and the profile's autocorrelation
|
||||
has **eight evenly spaced peaks, mean 2.177 s**, over nine revolutions.
|
||||
⚠️ Do not read the committed 20 s mean image as a frame: the spin averages to
|
||||
a uniform circle, which is why it looks headless. Five single frames 4 s apart
|
||||
show the head at five different angles.
|
||||
[`focus-ring-spin-measured.md`](../re/focus-ring-spin-measured.md) ·
|
||||
[frames](../re/captures/focus-ring/ring-single-frames-4s-apart.png)
|
||||
|
||||
* ✅ **And the ring is the ONLY thing that moves on the settled main menu.**
|
||||
Temporal std over 20 s untouched is **exactly 0.000** on every unfocused
|
||||
button, on the labels and on the `ptmsg` footer. Static menu + spinning ring
|
||||
draws everything that moves.
|
||||
|
||||
* **3 — the idle timer that made your Ⓑ rule unprovable is REFUTED on the main
|
||||
menu.** Held untouched, the menu stayed put for **≥ 60 s** (49 samples, menu
|
||||
correlation never leaving 0.9245–0.9249), against the "~8–10 s idle returns to
|
||||
the title" this page carried. ✅ That timer is real but belongs to the
|
||||
**title**, not the menu — the corpus had it attached to the wrong screen.
|
||||
🟡 Ⓑ itself: delivered (Canary logs `vk=5801`), and in both runs the only input
|
||||
in ≥ 100 s, followed by the title. **Ordering measured, timing not** — keep
|
||||
Ⓑ→title, now better supported than authored.
|
||||
[`menu-idle-and-b-2026-08-29.md`](../re/menu-idle-and-b-2026-08-29.md)
|
||||
|
||||
* **Your new #1 — the boot title shows build 4 FIRST, and the plate arrives
|
||||
after.** It is your third option, not the first two. Green-Ⓐ glyph count on the
|
||||
boot title went **154 → 781**, and 154 is the same reading the committed
|
||||
`live-title-build4-no-plate.png` gives (159) while plate titles give
|
||||
753/977/1493. 🔴 **The delay is NOT measured** — see the next bullet.
|
||||
|
||||
* 🔴 **Four durations I took the same day are WITHDRAWN, including the plate
|
||||
delay.** `screen_match.classify_array` costs **1503 ms/frame**; a probe running
|
||||
it per frame drained an 8 fps stream at **0.64 fps**, so its frames were stale
|
||||
and increasingly so. It manufactured "plate 24.66 s after the title art",
|
||||
"Ⓑ→title in 15.58 s", "Ⓑ→title in 25.60 s" and "Ⓐ→menu in 20.26 s". The tell:
|
||||
a transition, a press and a fade do not share a duration — a backlog does.
|
||||
A backlog **preserves ordering and destroys durations**, which is exactly why
|
||||
the sequence results above stand and the timings do not. Fixed (`fast=True`,
|
||||
38–75 ms, re-controlled 8/8 on both paths, agreeing to ±0.005); the ring's
|
||||
numbers are unaffected and that was checked, not assumed.
|
||||
|
||||
## Status
|
||||
|
||||
| | Question | State | Answer / link |
|
||||
@@ -926,7 +974,7 @@ here until 2026-08-28 and is now settled.)
|
||||
| 🟡 | **the paint-order tie-break** (Q3) | eight candidates refuted; costs one element's blend on one screen |
|
||||
| 🟡 | **GamePart ids behind the buttons** (Q4) | the *screens* are measured; the ids are a name match onto the executable's class names |
|
||||
| 🟡 | **the boot transitions in code** (Q6) | both levels decoded — phase at `this+132` (`entry→2`, `2→0`, `2→3`, `3→4`, `4→2`) and state at `this+136` inside phase 4. Phase 0 = splash (`LOGO`), phase 2 = title + `PRESS Ⓐ`, phase 4 = menu. Unknown: what the event *numbers* mean |
|
||||
| 🟡 | **Ⓑ leaving the main menu** (Q5) | **downgraded 2026-08-29.** The behaviour was seen once, with no capture, and the main menu is the only screen whose footer does not advertise Ⓑ — measured, **0** red-Ⓑ glyph pixels in the whole frame on two captures, against 514/518 on `EXTRAS`/`DIFFICULTY` with the same detector. An ~8–10 s idle also returns to the title, and nobody has separated the two. Implement it (it is the only exit) but treat it as **authored** — [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md#-refutation-attempt-2026-08-29--the-main-menus-own-footer-does-not-advertise-ⓑ) |
|
||||
| 🟡 | **Ⓑ leaving the main menu** (Q5) | **upgraded 2026-08-29 (later).** The idle half of this objection is **refuted**: the main menu does not self-return for **≥ 60 s** untouched, and the ~8–10 s idle belongs to the **title**. Ⓑ is delivered (Canary logs `vk=5801`) and is the only input in ≥ 100 s before the return, so the **ordering is measured**; the latency is not (a backlogged probe void). The footer point stands — the main menu is still the only screen not advertising Ⓑ — [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md#-refutation-attempt-2026-08-29--the-main-menus-own-footer-does-not-advertise-ⓑ) |
|
||||
| ❔ | **builds 0/1 and 10/11**, the `DELTASABER` plates (Q2) | never seen anywhere in the boot path, the title-side screens or the attract loop. A mission load is the remaining candidate and this container kills runs before one completes |
|
||||
|
||||
(An earlier version of this table called the audio items blocked on "an emulator
|
||||
|
||||
@@ -147,7 +147,7 @@ files, which is how the same ground got covered twice.
|
||||
| [`structures/unit-struct-runtime.md`](structures/unit-struct-runtime.md) | Runtime `Unit` struct (craft / vessel definitions) — read from live guest memory | — |
|
||||
| [`structures/weapon-struct-runtime.md`](structures/weapon-struct-runtime.md) | Runtime `Weapon` / `Shell` structs — read from live guest memory | — |
|
||||
| [`structures/xbg7-mesh.md`](structures/xbg7-mesh.md) | XBG7 — mesh geometry (inside XPR2 model containers) | — |
|
||||
| [`capture-harness-status.md`](capture-harness-status.md) | Why the harness stops reaching the title — and, since 2026-08-29, why there is no oracle at all | 🔴 **the decoder container has NO disc**: no ISO, no XEX, no pak anywhere on the filesystem. Root-caused to `06676d3` moving `/work` to a named volume without replacing the disc mount; `sylph-port` mounts it, `sylph-decoder` does not. Earlier sections: `screenshot` costs 10.8 s under xenia (92×), and `trace_gpu_stream` is a no-op in the Release build |
|
||||
| [`capture-harness-status.md`](capture-harness-status.md) | Why the harness stops reaching the title — and the two instruments that could not see the disc | ✅ **the disc is BACK** (2026-08-29, container replaced at 11:07:38): `/disc` is a real 6.2 GB read-only mount and `screen list` returns 12 builds. The "no disc" section is withdrawn — and its two instruments were blind either way: `find / -xdev` cannot cross into a bind mount on another device, and `sylph-doctor` only ever looks under `/work`. Earlier sections: `screenshot` costs 10.8 s under xenia (92×), and `trace_gpu_stream` is a no-op in the Release build |
|
||||
| [`title-crash-stl-tree.md`](title-crash-stl-tree.md) | The title-screen crash is an STL `map`/`set` erase on a bad iterator | ✅ CONFIRMED — the guest throws std::out_of_range from an STL |
|
||||
| [`ui-paint-order-third-permutation.md`](ui-paint-order-third-permutation.md) | A third measured paint order — tool built and validated, screen not reached | ✅ the reader works and is CONFIRMED against both previously |
|
||||
| [`ui-quad-class-foothold.md`](ui-quad-class-foothold.md) | The guest's UI quad class — a foothold found from the capture's vertex layout | 🟡 PROBABLE for the identification below (it is a static read, but |
|
||||
@@ -162,3 +162,5 @@ files, which is how the same ground got covered twice.
|
||||
| [`upstream-baseline.md`](upstream-baseline.md) | A stock-upstream baseline runs Stage 02 crash-free | ✅ CONFIRMED — upstream canary_experimental + only the pad |
|
||||
| [`weapon-datasheet-runtime.md`](weapon-datasheet-runtime.md) | Weapon DATA SHEET — runtime capture (Route B) | 🟡 first dynamic capture, 2026-07-28. The Arsenal's Gallery Mode panel is a |
|
||||
| [`xpr2-colour-check.md`](xpr2-colour-check.md) | XPR2 colours: channel order ✅ confirmed against the running game | — |
|
||||
| [`focus-ring-spin-measured.md`](focus-ring-spin-measured.md) | The main menu's focus ring spins continuously — and how fast | ✅ **measured**: period **2.177 s** over 9 revolutions (8 evenly spaced autocorrelation peaks) = 120 units = 60 frames = 2.00 s at 30 Hz. A pulse is excluded — annulus total conserved to 0.4 % while per-bin brightness swings by 24. ✅ the ring is the **only** moving thing on the settled main menu (std exactly 0.000 elsewhere). 🔴 no angle is quoted: the angular estimator FAILED its own control (30° → 0°) |
|
||||
| [`menu-idle-and-b-2026-08-29.md`](menu-idle-and-b-2026-08-29.md) | The main menu does not idle back to the title — and four durations that were a pipeline | ✅ **refuted**: no self-return in **≥ 60 s** untouched; the ~8–10 s idle belongs to the **title**. 🟡 Ⓑ→title ordering measured, latency not. 🔴 `classify_array` at **1503 ms/frame** drained an 8 fps stream at 0.64 fps and manufactured four latencies (24.66 s / 15.58 s / 25.60 s / 20.26 s) — all withdrawn; a backlog preserves ordering and destroys durations |
|
||||
|
||||
@@ -617,3 +617,35 @@ neighbourhood, not just the line.
|
||||
`10 144 of 10 148 references resolve`) → **withdrawn; it is on the disc.** It is
|
||||
the `GP_STAGE_CLEAR` child the same scan named `8AX`. With the name decoded the
|
||||
count is **10 148 of 10 148**. [`ratc-child-names.md`](structures/ratc-child-names.md)
|
||||
|
||||
## The oracle harness and the container (2026-08-29)
|
||||
|
||||
* "the decoder container has no disc" → **refuted the same day.** The container
|
||||
was replaced and `/disc` is a real 6.2 GB read-only mount. Worse, both
|
||||
instruments behind the claim were blind to the answer either way:
|
||||
`find / -xdev` **cannot cross** into a bind mount on another device, and
|
||||
`sylph-doctor` only checks `/work` and never `$SYLPHEED_DISC`. "sylph-doctor
|
||||
agrees" was two instruments sharing one blind spot.
|
||||
→ To test for the disc, ask the variable that names it:
|
||||
`sylpheed-cli screen list "$SYLPHEED_DISC/dat/GP_TITLE.pak"`.
|
||||
* "the main menu returns to the title on its own after ~8–10 s idle" → **refuted.**
|
||||
The menu sat untouched for **≥ 60 s** without moving (correlation never leaving
|
||||
0.9245–0.9249). The ~8–10 s idle is real but belongs to the **title**. This was
|
||||
the only reason "Ⓑ leaves the main menu" was classed as authored.
|
||||
* "whole-image statistics (green / white / mean) can tell the title from the
|
||||
attract movie" → **refuted.** A frame of `ADV.wmv` with a bright green laser
|
||||
reads green 0.0018 / white 0.086 / mean (53,67,76) — the title's numbers. A
|
||||
probe built on it tapped Ⓐ into the movie and waited 120 s for a menu that was
|
||||
never coming. → Correlate against a committed capture instead, and keep movie
|
||||
frames as the negative controls.
|
||||
* "a 360-bin angular cross-correlation can measure the focus ring's rotation
|
||||
angle" → **refuted by its own control**: a synthetic **30°** rotation of a live
|
||||
frame came back as **0°** (peak 0.596), while 90/180/270° came back exactly
|
||||
(peak 1.000) — it only resolves exact pixel permutations. No angle was quoted;
|
||||
the spin was established from brightness conservation instead.
|
||||
* "a latency read off a classified `x11grab` stream is a duration" → **refuted.**
|
||||
At 1503 ms per classification against an 8 fps stream the consumer ran at
|
||||
0.64 fps, so frames were stale and increasingly so. Four "durations" died with
|
||||
it. The tell was that a screen transition, a button press and a plate fade all
|
||||
came out at ~20–25 s. → A backlog **preserves ordering and destroys
|
||||
durations**; check consumed-fps against requested-fps before quoting a time.
|
||||
|
||||
120
docs/re/menu-idle-and-b-2026-08-29.md
Normal file
120
docs/re/menu-idle-and-b-2026-08-29.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# 🔴 The main menu does NOT self-return to the title — and three "latencies" were my own pipeline
|
||||
|
||||
**Status:** one ✅ **measured** negative, one 🟡 ordering-only result, and one
|
||||
🔴 **instrument defect that voids three numbers I took the same day.** Taken
|
||||
2026-08-29.
|
||||
|
||||
## ✅ Refuted: "an ~8–10 s idle returns to the title" does not apply to the main menu
|
||||
|
||||
HANDOFF's residue table downgraded *Ⓑ leaves the main menu* to **authored**, on
|
||||
the grounds that "an ~8–10 s idle also returns to the title, and nobody has
|
||||
separated the two". That reason is now gone.
|
||||
|
||||
**Measured:** the main menu was held with **no input at all** and classified every
|
||||
~1.2 s by [`screen_match.py`](../../tools/re-capture/screen_match.py):
|
||||
|
||||
| phase | duration untouched | screens seen |
|
||||
|---|---|---|
|
||||
| period capture | 24 s | menu only |
|
||||
| idle probe | **60 s** (49 samples) | **menu only** |
|
||||
|
||||
Correlation against the committed main-menu capture never moved outside
|
||||
**0.9245 – 0.9249** across the whole idle window — not a drift, not a fade, a
|
||||
screen sitting still. Conservatively that is **≥ 60 s of continuous idle with no
|
||||
self-return**, against a claim of 8–10 s.
|
||||
|
||||
✅ **And the 8–10 s idle is real — it belongs to the TITLE.** Immediately after
|
||||
this run, a probe that expected to find the title still on screen found it had
|
||||
left on its own into the attract movie. So the corpus's idle timer is a property
|
||||
of the **title screen** (title → `ADV.wmv` → title), and the residue table
|
||||
attached it to the wrong screen.
|
||||
|
||||
**What this gives the port:** the idle alternative that made Ⓑ unprovable is
|
||||
refuted *on the screen in question*. Ⓑ is no longer competing with a timer there.
|
||||
|
||||
## 🟡 Ⓑ on the main menu: the ordering survives, the timing does not
|
||||
|
||||
Ⓑ was **delivered** — Canary's own log records `[file-pad] keystroke vk=5801
|
||||
down` / `up` and `XamInputGetKeystrokeEx -> user=0 vk=5801`, so this is not a
|
||||
dropped press. In both runs the menu was followed by the title, and in both runs
|
||||
**Ⓑ was the only input** in a window of ≥ 100 s either side.
|
||||
|
||||
🟡 **But it is still two observations with a confound I cannot yet exclude**, and
|
||||
the reason is the next section: the "latency" I measured is worthless, so I
|
||||
cannot say the return followed Ⓑ *promptly*. What stands is ordering plus the
|
||||
absence of any other cause:
|
||||
|
||||
* no input for ≥ 100 s before → no transition;
|
||||
* Ⓑ delivered → transition to the title.
|
||||
|
||||
**Classification: measured ordering, unmeasured timing.** The port should keep
|
||||
Ⓑ→title, and it is now better supported than "authored" — but it is not yet a
|
||||
timed measurement.
|
||||
|
||||
## 🔴 The defect: an oracle that cost 1503 ms per frame produced three fake latencies
|
||||
|
||||
`screen_match.classify_array` does a ±8 px ZNCC search over a 675×1279 surface
|
||||
against two references. **Measured: 1503 ms per frame.** A probe calling it on
|
||||
every frame of an 8 fps `x11grab` stream therefore drained the pipe at
|
||||
**0.64 frames/s** — verified from the probe's own trace, 107 samples over 166 s.
|
||||
|
||||
The pipe backed up at ~7.4 fps, so every frame classified was **stale, and
|
||||
increasingly so**. That is not a subtle bias; it manufactured three numbers:
|
||||
|
||||
| reported | actually |
|
||||
|---|---|
|
||||
| plate appears 24.66 s after the title art | unknown |
|
||||
| Ⓑ → title in 15.58 s (run 1) | unknown |
|
||||
| Ⓑ → title in 25.60 s (run 2) | unknown |
|
||||
| Ⓐ → menu in 20.26 s | unknown |
|
||||
|
||||
🔴 **All four are withdrawn.** The tell was that they are all ~20–25 s: a screen
|
||||
transition, a button press and a plate fade do not share a duration, but a
|
||||
backlog does. The two Ⓑ figures *growing* 15.6 → 25.6 s across a longer run is
|
||||
the backlog accumulating, and it is the signature to remember.
|
||||
|
||||
⚠️ **What a backlog does and does not destroy.** It delays every frame by the
|
||||
same growing amount, so it **preserves ordering** and destroys **durations**.
|
||||
That is why the ordering results above survive and every duration here does not.
|
||||
|
||||
✅ **Fixed and re-controlled.** `screen_match` now has a `fast=True` path
|
||||
(4× decimation, ±2 decimated px) at **38–75 ms**, a 20–60× reduction, and the
|
||||
control was re-run on **both** paths: 8/8 each, with the fast path agreeing with
|
||||
the exact path to **±0.005** on every score.
|
||||
|
||||
✅ **The ring measurements are NOT affected**, and this was checked rather than
|
||||
assumed: `ring_period.py` does a greyscale conversion and a crop per frame, and
|
||||
achieved **15.03 fps against a requested 15** — it kept up exactly, so its
|
||||
timestamps carry no backlog. Its period also has an internal check a drifting
|
||||
clock cannot pass: eight *evenly spaced* autocorrelation peaks
|
||||
([`focus-ring-spin-measured.md`](focus-ring-spin-measured.md)).
|
||||
|
||||
## 🟡 The `PRESS Ⓐ` plate: sequence answered, duration not
|
||||
|
||||
The port asked whether the boot title is build 4 alone, build 4 with the plate
|
||||
composited from the start, or build 4 **then** the plate after a delay.
|
||||
|
||||
✅ **It is the third.** On the boot title the green-Ⓐ glyph count went
|
||||
**154 → 781** with the title art already matching at 0.946. The 154 is the
|
||||
decisive number: the committed no-plate capture
|
||||
`live-title-build4-no-plate.png` reads **159** with the same counter, and plate
|
||||
titles read 753 / 977 / 1493. So the title genuinely presents **without** the
|
||||
plate first, and the plate arrives afterwards.
|
||||
|
||||
🔴 **How long afterwards is NOT measured** — that figure came from the backlogged
|
||||
probe and is withdrawn with the rest. The port needs one more run with the fast
|
||||
path to get it.
|
||||
|
||||
## Instrument controls, now committed
|
||||
|
||||
The negative controls for `screen_match` are **movie frames**, because that is
|
||||
the class the oracle exists to reject — a statistics-based oracle
|
||||
(green/white/mean) called a frame of `ADV.wmv` containing a bright green laser
|
||||
`title`, and a probe built on it tapped Ⓐ into the movie and then waited 120 s
|
||||
for a menu that was never coming.
|
||||
|
||||
An earlier version of the control list pointed at two **scratch** grabs, and a
|
||||
later run of the same probe overwrote one of them — turning a negative control
|
||||
into a title frame and failing the control for the wrong reason. They are now
|
||||
committed fixtures under
|
||||
[`captures/instrument-controls/`](captures/instrument-controls/).
|
||||
87
tools/re-capture/menu_b_probe.py
Normal file
87
tools/re-capture/menu_b_probe.py
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Does (B) leave the main menu -- and does the menu self-return to the title?
|
||||
|
||||
HANDOFF downgraded "(B) on the main menu returns to the title" to authored,
|
||||
because the corpus also carries "an ~8-10 s idle returns to the title" and one
|
||||
unrecorded observation cannot separate the two causes. This separates them by
|
||||
ordering: hold the menu UNTOUCHED for an idle window several times longer than
|
||||
the claimed 8-10 s and timestamp what happens, THEN press (B) and timestamp
|
||||
again. If the idle window passes with the menu still up, the idle cause is
|
||||
gone and the (B) observation is unambiguous.
|
||||
|
||||
Screen identity comes from screen_match.py, whose control includes the movie
|
||||
frames that broke the statistics-based oracle.
|
||||
|
||||
Usage: menu_b_probe.py IDLE_SECONDS AFTER_SECONDS
|
||||
"""
|
||||
import os, subprocess, sys, time
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
SD = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, SD)
|
||||
from screen_match import classify_array
|
||||
|
||||
W, H = 1280, 720
|
||||
IDLE = float(sys.argv[1]) if len(sys.argv) > 1 else 60.0
|
||||
AFTER = float(sys.argv[2]) if len(sys.argv) > 2 else 30.0
|
||||
OUT = "/sylph-home/re/ringcap"
|
||||
|
||||
|
||||
def stream():
|
||||
return subprocess.Popen(
|
||||
["ffmpeg", "-loglevel", "error", "-f", "x11grab", "-draw_mouse", "0",
|
||||
"-video_size", f"{W}x{H}", "-i", ":98", "-r", "4",
|
||||
"-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
|
||||
stdout=subprocess.PIPE, bufsize=W * H * 3 * 2)
|
||||
|
||||
|
||||
def main():
|
||||
p = stream(); n = W * H * 3
|
||||
t0 = time.time(); seg = t0; last = None; prev = None
|
||||
phase = "IDLE"; pressed_at = None
|
||||
log = []
|
||||
while True:
|
||||
el = time.time() - t0
|
||||
if phase == "IDLE" and el >= IDLE:
|
||||
subprocess.run(["python3", f"{SD}/pad.py", "tap", "B", "0.3"], check=False)
|
||||
pressed_at = time.time() - t0
|
||||
print(f"t={pressed_at:6.2f}s >>> (B) PRESSED", flush=True)
|
||||
phase = "AFTER"
|
||||
if phase == "AFTER" and el >= IDLE + AFTER:
|
||||
break
|
||||
if time.time() - seg > 25:
|
||||
p.kill(); p = stream(); seg = time.time()
|
||||
b = p.stdout.read(n)
|
||||
if len(b) < n:
|
||||
p.kill(); p = stream(); seg = time.time(); continue
|
||||
a = np.frombuffer(b, np.uint8).reshape(H, W, 3)
|
||||
last = a
|
||||
c, sc = classify_array(a)
|
||||
log.append((el, c, sc["title"], sc["menu"]))
|
||||
if c != prev:
|
||||
print(f"t={el:6.2f}s screen={c:<6} title={sc['title']:+.3f} "
|
||||
f"menu={sc['menu']:+.3f}", flush=True)
|
||||
Image.fromarray(a).save(f"{OUT}/b-{el:06.2f}-{c}.png")
|
||||
prev = c
|
||||
p.kill()
|
||||
with open(f"{OUT}/menu-b-trace.tsv", "w") as f:
|
||||
f.write("t_s\tscreen\tcorr_title\tcorr_menu\n")
|
||||
for r in log:
|
||||
f.write(f"{r[0]:.3f}\t{r[1]}\t{r[2]:.4f}\t{r[3]:.4f}\n")
|
||||
idle = [r for r in log if r[0] < IDLE]
|
||||
aft = [r for r in log if pressed_at and r[0] > pressed_at + 1.0]
|
||||
print(f"\nIDLE phase : {len(idle)} samples over {IDLE:.0f}s, "
|
||||
f"screens seen = {sorted(set(r[1] for r in idle))}")
|
||||
print(f"AFTER (B) : {len(aft)} samples, "
|
||||
f"screens seen = {sorted(set(r[1] for r in aft))}")
|
||||
first_title = next((r[0] for r in aft if r[1] == "title"), None)
|
||||
if first_title:
|
||||
print(f" first 'title' at t={first_title:.2f}s = "
|
||||
f"{first_title - pressed_at:.2f}s after the (B) press")
|
||||
print(f"trace: {OUT}/menu-b-trace.tsv")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
119
tools/re-capture/title_plate_and_b_probe.py
Normal file
119
tools/re-capture/title_plate_and_b_probe.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""One boot, two answers: when the PRESS (A) plate appears, and what (B) does.
|
||||
|
||||
Q(plate): the port's boot ends on GP_TITLE build 4, which carries no plate, and
|
||||
(A) is the only way off it -- so it ships a screen that needs a press and does
|
||||
not say so. Builds 2/3 are the plate. The open half is the SEQUENCE: build 4
|
||||
alone, build 4 with the plate composited from the start, or build 4 and THEN
|
||||
the plate after a delay. This logs the title-art correlation and the green-(A)
|
||||
glyph count on EVERY frame from before the title appears, so the two crossings
|
||||
are read off one trace rather than inferred.
|
||||
|
||||
Q(B): whether (B) leaves the main menu, timed against the idle alternative.
|
||||
|
||||
Controls, both pre-run on committed captures:
|
||||
* screen identity -- screen_match.py, 8/8 including the movie frames that
|
||||
broke the statistics oracle;
|
||||
* the plate -- green-(A) glyph count: 753/977/1493 px on plate titles, 159 on
|
||||
`live-title-build4-no-plate.png`, 327 on the main menu. Threshold 400.
|
||||
|
||||
Usage: title_plate_and_b_probe.py OUTDIR
|
||||
"""
|
||||
import os, subprocess, sys, time
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
SD = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, SD)
|
||||
from screen_match import classify_array
|
||||
|
||||
W, H = 1280, 720
|
||||
PLATE = 400
|
||||
OUT = sys.argv[1] if len(sys.argv) > 1 else "/sylph-home/re/platecap"
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
|
||||
def stream():
|
||||
return subprocess.Popen(
|
||||
["ffmpeg", "-loglevel", "error", "-f", "x11grab", "-draw_mouse", "0",
|
||||
"-video_size", f"{W}x{H}", "-i", ":98", "-r", "8",
|
||||
"-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
|
||||
stdout=subprocess.PIPE, bufsize=W * H * 3 * 2)
|
||||
|
||||
|
||||
def glyph(a):
|
||||
r, g, b = a[:, :, 0].astype(int), a[:, :, 1].astype(int), a[:, :, 2].astype(int)
|
||||
return int(((g > 130) & (g - r > 45) & (g - b > 45)).sum())
|
||||
|
||||
|
||||
def main():
|
||||
p = stream(); n = W * H * 3
|
||||
t0 = time.time(); seg = t0; prev = None
|
||||
skipped = False; stage = 0; marks = {}
|
||||
rows = []
|
||||
while time.time() - t0 < 420:
|
||||
if time.time() - seg > 25:
|
||||
p.kill(); p = stream(); seg = time.time()
|
||||
b = p.stdout.read(n)
|
||||
if len(b) < n:
|
||||
p.kill(); p = stream(); seg = time.time(); continue
|
||||
a = np.frombuffer(b, np.uint8).reshape(H, W, 3)
|
||||
el = time.time() - t0
|
||||
c, sc = classify_array(a)
|
||||
gl = glyph(a)
|
||||
rows.append((el, c, sc["title"], sc["menu"], gl))
|
||||
if c != prev:
|
||||
print(f"t={el:7.2f}s screen={c:<6} title={sc['title']:+.3f} "
|
||||
f"menu={sc['menu']:+.3f} glyph={gl}", flush=True)
|
||||
prev = c
|
||||
|
||||
if not skipped and el > 45:
|
||||
subprocess.run(["python3", f"{SD}/pad.py", "tap", "A", "0.3"], check=False)
|
||||
skipped = True
|
||||
print(f"t={el:7.2f}s one (A) to skip the intro movie", flush=True)
|
||||
elif stage == 0 and c == "title":
|
||||
marks["title_art"] = el; stage = 1
|
||||
Image.fromarray(a).save(f"{OUT}/title-first-{el:07.2f}.png")
|
||||
print(f"t={el:7.2f}s TITLE ART (glyph={gl}) — watching for the plate",
|
||||
flush=True)
|
||||
elif stage == 1 and gl >= PLATE:
|
||||
marks["plate"] = el; stage = 2
|
||||
Image.fromarray(a).save(f"{OUT}/title-plate-{el:07.2f}.png")
|
||||
print(f"t={el:7.2f}s PLATE (glyph={gl}) — "
|
||||
f"{el-marks['title_art']:.2f}s after the title art", flush=True)
|
||||
elif stage == 2 and el > marks["plate"] + 6:
|
||||
subprocess.run(["python3", f"{SD}/pad.py", "tap", "A", "0.3"], check=False)
|
||||
marks["A"] = el; stage = 3
|
||||
print(f"t={el:7.2f}s >>> (A) on the title", flush=True)
|
||||
elif stage == 3 and c == "menu":
|
||||
marks["menu"] = el; stage = 4
|
||||
print(f"t={el:7.2f}s MENU — idling 25 s before (B)", flush=True)
|
||||
elif stage == 4 and el > marks["menu"] + 25:
|
||||
subprocess.run(["python3", f"{SD}/pad.py", "tap", "B", "0.3"], check=False)
|
||||
marks["B"] = el; stage = 5
|
||||
print(f"t={el:7.2f}s >>> (B) PRESSED", flush=True)
|
||||
elif stage == 5 and c != "menu":
|
||||
marks["left_menu"] = el
|
||||
print(f"t={el:7.2f}s LEFT THE MENU -> {c}, "
|
||||
f"{el-marks['B']:.2f}s after (B)", flush=True)
|
||||
stage = 6
|
||||
elif stage == 6 and el > marks["left_menu"] + 12:
|
||||
break
|
||||
|
||||
p.kill()
|
||||
with open(f"{OUT}/trace.tsv", "w") as f:
|
||||
f.write("t_s\tscreen\tcorr_title\tcorr_menu\tglyph\n")
|
||||
for r in rows:
|
||||
f.write(f"{r[0]:.3f}\t{r[1]}\t{r[2]:.4f}\t{r[3]:.4f}\t{r[4]}\n")
|
||||
print("\nmarks:", {k: round(v, 2) for k, v in marks.items()})
|
||||
if "title_art" in marks and "plate" in marks:
|
||||
print(f"PLATE DELAY: {marks['plate']-marks['title_art']:.2f}s "
|
||||
f"after the title art first matched")
|
||||
if "B" in marks and "left_menu" in marks:
|
||||
print(f"(B) -> left the menu in {marks['left_menu']-marks['B']:.2f}s")
|
||||
print(f"trace: {OUT}/trace.tsv")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user