diff --git a/docs/re/structures/plate-pulse-measured.md b/docs/re/structures/plate-pulse-measured.md index dbe43cac..a611d78e 100644 --- a/docs/re/structures/plate-pulse-measured.md +++ b/docs/re/structures/plate-pulse-measured.md @@ -90,6 +90,24 @@ may already be an attract-loop title rather than the boot's first. Both windows "the title, held, with no input", which is what was asked; neither is proof about the *first* appearance specifically. +## ✅ Why the floor is 714 and not 159 — the port's mechanism, and it fits + +The port agent implemented the pulse and reports the shape that reproduces these +two levels: a **steady base plus a pulsing glow**, not a glow alone. + +* `ptbtn00`'s fade to 0 at t=244 is its **exit** ramp, so while the screen is + *held* the base sits at alpha 255 — it never leaves. +* `ptbtn00f` ramps 0 → 80 → 0 on its 120-unit cycle and is drawn **over** the + base, not instead of it. + +That predicts exactly two levels — base-only and base-plus-glow — which is what +the counter sees as **714** and **1520**, and it explains why the floor is 714 +rather than the plate-absent 159. ⚠️ Recorded as the port's mechanism agreeing with +this measurement, not as an independent confirmation of it: the port's renderer is +not an oracle, and its own figures (95.68 / 115.52 in the plate region) are its +render's, not the game's. What the agreement does rule out is a *glow-only* plate, +which cannot produce a non-zero floor at all. + ⚠️ **The glyph count is a thresholded pixel count, not an alpha.** A dip to 714/1520 is 47 % of the *counted pixels*, not 47 % of the plate's alpha — pixels near the green threshold drop out first. Do not read a duty cycle or an alpha ramp diff --git a/docs/re/structures/title-a-press-fault.md b/docs/re/structures/title-a-press-fault.md index a8c4cf7c..e0fad5fb 100644 --- a/docs/re/structures/title-a-press-fault.md +++ b/docs/re/structures/title-a-press-fault.md @@ -151,16 +151,39 @@ connect to it. [`canary-scripted-input-traps.md`](../canary-scripted-input-traps `tools/re-capture/boot_menu.sh`'s own header has carried the mechanism, **including the 8.4 million figure**, since before this page was written. -### The run's own config dump says the profile was not signed in +### 🔴 RETRACTED — "the run's config dump says the profile was not signed in" -``` -[Profiles] -create_profile_if_none = "" -logged_profile_slot_0_xuid = "" ← nobody logged in -... -i> FindProfiles: Adding profile B13EBABEBABEBABE to profile list -i> ProfileManager: Found 1 Profiles ← a profile EXISTS, it is just not signed in -``` +This section read the faulting run's `[Profiles]` block — +`logged_profile_slot_0_xuid = ""` — as evidence that nobody was signed in. +**That inference is wrong, and I refuted it with a direct test.** + +Xenia prints its config dump **before applying command-line overrides**. In a run +launched with `--apu=sdl --hid=file --mute=true --log_mask=13`, the dump says: + +| dumped | actually passed | +|---|---| +| `apu = "any"` | `--apu=sdl` | +| `hid = "any"` | `--hid=file` | +| `mute = false` | `--mute=true` | +| `log_mask = 0` | `--log_mask=13` | + +Four for four. **The dump is the config *file*, not the run.** So +`logged_profile_slot_0_xuid = ""` says only that the file is empty; the faulting +run may well have had the flag on its command line, and this page cannot tell. + +⚠️ **Anything in this corpus that cites a Canary config dump as evidence of what a +run did is making the same mistake.** The dump is a statement about +`xenia-canary.config.toml`. To know a run's settings, record its **argv**. + +What survives untouched: the *mechanism* (swallow → unbounded pump → failed +allocation → fault), which rests on the `[RE-INPUT]` log counter and the crash +dump's register file, neither of which is a config dump; and +[`canary-scripted-input-traps.md`](../canary-scripted-input-traps.md) §3's measured +claim that a profile-less Ⓐ pops the sign-in dialog, which is somebody else's +observation with a capture behind it. + +What does **not** survive: this page's claim to know the faulting run's profile +state. It does not. ### And the call site is now located in the image, not only observed diff --git a/tools/re-capture/wait_and_press.py b/tools/re-capture/wait_and_press.py new file mode 100755 index 00000000..5d8bcfc7 --- /dev/null +++ b/tools/re-capture/wait_and_press.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Wait for the interactive title, press Ⓐ once, and watch what happens. + +The A/B this exists for: does a signed-in profile prevent the Ⓐ fault +(`docs/re/structures/title-a-press-fault.md`)? A press sent before the title is +up tests nothing, so the wait is a **control on the press**, not a convenience — +`is_title.py`'s glyph counter has to clear its threshold first. + + wait_and_press.py OUTDIR [wait_s] [watch_s] + +🔴 The first version of this used a SINGLE frame over a glyph threshold, and it +fired on the intro MOVIE — twice, voiding both legs of an A/B. The movie throws +green flashes of 1298…5433 lasting under a second, which clears any threshold the +title also clears. This is the same trap `is_title.py` records `screen_id.py` +falling into (the SQUARE ENIX logo called "title" 151 s in). + +The title is distinguished by the **plate's pulse**, not by brightness: a +sustained oscillation that never drops below ~700 and never exceeds ~1600 +(`docs/re/structures/plate-pulse-measured.md` measured 714…1520). So the detector +requires HOLD consecutive samples inside a band. A movie flash is 2–4 samples and +overshoots the top of it. +""" +import os +import subprocess +import sys +import time + +import numpy as np +from PIL import Image + +OUT = sys.argv[1] +WAIT = float(sys.argv[2]) if len(sys.argv) > 2 else 420 +WATCH = float(sys.argv[3]) if len(sys.argv) > 3 else 90 +W, H = 1280, 720 +NEED = 500 # the plate is up; the floor with no plate is 159 +CEIL = 2500 # a movie flash overshoots this; the plate peaks at ~1520 +HOLD = 12 # consecutive in-band samples (~3 s at 4 Hz) + + +def _open(): + 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 glyph(a): + r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2] + return int(((g > 130) & (g - r > 45) & (g - b > 45)).sum()) + + +p, n, t0, seg = _open(), W * H * 3, time.time(), time.time() +log = open(f"{OUT}/series.tsv", "w") +log.write("# t_s\tglyph\tmean\tphase\n") +phase, pressed_at, streak = "wait", None, 0 +while True: + el = time.time() - t0 + if phase == "wait" and el > WAIT: + print(f"TITLE NEVER APPEARED in {WAIT}s"); break + if phase == "watch" and time.time() - pressed_at > WATCH: + print("watch window complete"); break + if time.time() - seg > 30: + p.kill(); p = _open(); seg = time.time() + buf = p.stdout.read(n) + if len(buf) < n: + p.kill(); p = _open(); seg = time.time(); continue + a = np.frombuffer(buf, np.uint8).reshape(H, W, 3).astype(int) + c = glyph(a) + log.write(f"{el:.3f}\t{c}\t{a.mean():.3f}\t{phase}\n"); log.flush() + if phase == "wait": + streak = streak + 1 if NEED <= c <= CEIL else 0 + if phase == "wait" and streak >= HOLD: + Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/before-press.png") + print(f"TITLE UP at {el:.1f}s (glyph {c}, {streak} in-band samples) — pressing A") + subprocess.run([sys.executable, + os.path.join(os.path.dirname(os.path.abspath(__file__)), "pad.py"), + "tap", "A", "0.12"], check=False) + pressed_at = time.time(); phase = "watch" +p.kill() +# a last frame, whatever state it ended in +try: + p2 = _open(); buf = p2.stdout.read(n); p2.kill() + if len(buf) == n: + a = np.frombuffer(buf, np.uint8).reshape(H, W, 3).astype(int) + Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/after-watch.png") + print(f"final glyph {glyph(a)} mean {a.mean():.1f}") +except Exception as e: + print("final grab failed:", e) +log.close()