diff --git a/docs/agents/CONTAINER-NOTES.md b/docs/agents/CONTAINER-NOTES.md index dc4a5b95..5e0f8cf3 100644 --- a/docs/agents/CONTAINER-NOTES.md +++ b/docs/agents/CONTAINER-NOTES.md @@ -206,3 +206,33 @@ XEX decrypt + LZX decompress, and the disassembly-to-database step β€” belongs i `crates/sylpheed-formats`.** Until then, every static finding rests on an artefact this project cannot rebuild, and that is a real gap in the corpus rather than a convenience. + +## πŸ”΄ Pressing β’Ά on the title faults the guest β€” and the fault fills the disk + +Three attempts to capture the main menu on 2026-08-29 ended the same way. Every +run that tapped β’Ά **on the title** faulted; every run that tapped nothing there +completed and produced its capture. + +| run | input on the title | outcome | +|---|---|---| +| 1 | β’Ά, then β’Ά again on the transition | guest fault, **519 MB** of register dump | +| 2 | one β’Ά | drifted to a `flight` classification, 97 MB | +| 3 | one β’Ά | guest fault, **223 MB** of register dump | +| 4–6 | none (`NOTAP=1`) | all completed normally | + +This is the crash `ui_draw_capture.sh`'s own header records from 2026-08-18 β€” "a +stray A there sends the guest into the save-data probe". ⚠️ The corpus's existing +menu measurements (Q4, Q5, the focus ring) were taken by some route that survived +this; what differs has not been found. **Menu-side dynamic RE is blocked until it +is.** + +⚠️ **A guest fault writes an UNBOUNDED register dump to stdout.** Xenia runs with +`break_on_unimplemented_instructions = true`, and the dump is `vN = [...]` / `rN = +...` lines at roughly 100 MB per 30 s. The filesystem here sits at **91 %**. Any +scripted run that presses a button must watch `canary.stdout` and kill on growth β€” +`ls -la` on it before trusting a long run. + +πŸ“Œ Two knobs added to `ui_draw_capture.sh` for boot-side work: `GRACE=1` (the fixed +8 s wait before arming means an `ARM=early` capture otherwise misses both splashes, +which run at ~1.2–9.5 s of guest time) and `NOTAP=1` (no input at all β€” the movie +tap fires on "the screen changed a lot", which is also true of a fading splash). diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index b4517fc5..455933e0 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -2297,3 +2297,47 @@ sharper form is that they were not two witnesses. They shared `implied_layer_key so the agreement carried no information β€” the only thing that could catch it was that the agreed answer was impossible on its face. +## 2026-08-29 β€” the 114-vs-120 gap was mine, and it is closed + +βœ… **The declared 120 stands. Nothing you ship changes.** The gap I flagged as +unexplained was a category error in my own arithmetic. + +**What I found in the draw stream:** `GP_TITLE` build 4 declares `t = 0…269` β€” +about 120 presented frames at this run's pacing β€” and the title dwell lasted +**~1 100**. `ptcopyright` declares alpha β‰₯ 1 for **106 units** and is drawn for +**1 050 frames**; `ptlogo1` declares an exit at t=264 and is drawn for 1 095. Both +disappear within three frames of the dwell ending. + +> **The top-level clock advances through the build-in, stops inside the settle +> window `[160, 236]`, and holds. The exit ramp is not on a timer β€” it plays when +> something makes the screen leave.** + +That is the settle-time decode observed from the other side, in the game rather +than in the file β€” and it is worth having explicitly if you drive transitions: +**do not schedule a screen's exit off its own timeline.** + +πŸ”΄ **And it explains the 114.** My 2.231 units/frame was regressed over *build-in* +events β€” the only stretch where the top-level clock advances β€” and I applied it to +the glow's period, measured over the settled dwell where that clock is frozen and +only the record's own clock runs. Two different clocks. The 120 was never in doubt +from the dark-fraction test, which needs no conversion at all. + +βœ… The 51.158-frame period is now confirmed by a **second independent estimator** +(autocorrelation: lag 51, harmonics at 102 and 154). + +❔ **The sweeps' period is still unmeasured**, and I would rather say so than give +you a number: the same estimator disagrees between two dwells of the same screen +(515 vs 452 frames). Combined with the zero-slack `+0x08`, neither the file nor +this capture settles whether they loop. **On the title they demonstrably do not +park**; the menu remains open. + +πŸ”΄ **Blocker you should know about, because it bounds what I can answer:** a single +β’Ά press on the title **faults the guest** in this container. Three menu-capture +attempts, two ending in register dumps of 223 MB and 519 MB, against three runs in +the same session that pressed nothing and all completed. It is the crash the +capture script's own header records from 2026-08-18. Menu-side dynamic RE is +blocked here until that is understood; the corpus's existing menu measurements +predate it. + +Detail: [`docs/re/structures/ui-clock-freezes-at-settle.md`](../re/structures/ui-clock-freezes-at-settle.md). + diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index dbbf5821..6add0ae8 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -175,3 +175,4 @@ files, which is how the same ground got covered twice. | [`structures/boot-splash-gap-measured.md`](structures/boot-splash-gap-measured.md) | The black gap between the two boot splashes | βœ… **measured** in the guest's **draw stream**, which separates true black from a fade tail where luminance cannot: the publisher's last sprite is frame 125 (alpha 7), then **frames 126–129 submit NO sprite quad at all**, then the developer fades in at alpha 34. **The gap is 4 presented frames.** Converted with the disc as its own clock β€” `palogo_sqex` declares alphaβ‰₯1 for **239.8 units** and is drawn in **105** frames β†’ **2.284 units/frame** (the title capture independently gave 2.231) β€” that is **~9.1 units β‰ˆ 0.152 s**, against the **12** the port authored; ⚠️ and the true black is *shorter*, since both boundary frames still carry picture. πŸ”΄ **RETRACTED**: "the developer splash is ONE composited 525Γ—259 quad" β€” the same batching artefact. It draws three logos and three glows as separate quads in one `indices=24` call; the 525Γ—259 was `gamearts_eff` merged with `seta_eff`. The port refuted it with arithmetic (a 259-tall box cannot hold logos spanning y 164…585) before I checked. ⚠️ The gap measurement is unaffected β€” those glows are the developer splash's first draw. ❌ Not declared on the disc: `palogo_eff0.prm` is a single static keyframe, and the top-level `+0x08` is a **family constant** (300 / 60) whose slack ranges 12–226 units. ❔ The executable is **not** looked at β€” named, not claimed. πŸ”΄ The instrument was perturbing the measurement: the capture script taps β’Ά on "screen changed a lot", which is also true of a fading splash β€” it tapped through the publisher and the developer never appeared. `GRACE=1` and `NOTAP=1` knobs added | | [`structures/ui-forced-backdrop.md`](structures/ui-forced-backdrop.md) | Where a keyless primitive paints, when the file forces it | βœ… **decoded**, partly closing `ui-prm-primitives.md`'s standing blocker: **an element covering the screen and fully opaque at some instant cannot paint above anything visible then**, and where that set is *every* other element its position is **forced first**. Disc-wide **80** instances forced, 50 constrained but not forced, 0 unconstrained. βœ… **Two controls, both measured orders from the running game**: it reproduces `palogo_eff0.prm` = FIRST (opaque 211 instants, below 6/6) β€” which a **name**-based rule gets wrong, since it is named like an overlay β€” and permits `pteff00.prm` on top (opaque 2 instants, below 3/23), which is where it is measured. βœ… Answers the port's `build_12`/`build_15` blank-screen contradiction: `pgloading_eff00.prm` is forced first, 4/4. βœ… Explains 36 builds the corpus recorded as "one colour" with no cause β€” `pzeff00.prm` forced first 32/32, so **our own sort wiped them**. πŸ”΄ The rule's limit was found by its own test failing: applied to `.t32` sprites it claimed 22 must sort first against their own keys (`pneff01` 0xd850 at #8/13, `pbfriendly` 0x9230 at #17/49) β€” a sprite's *element* alpha says nothing about its *texture*'s coverage, so it is now restricted to untextured primitives. ⚠️ Assumes straight alpha-over; blend mode is still ❔. ⚠️ A lower bound, not an ordering. ⚠️ No new oracle run β€” the controls are prior measurements | | [`structures/ui-forced-backdrop.md`](structures/ui-forced-backdrop.md) *(span sensitivity)* | How much of the forced-backdrop rule rests on the timeline convention | βœ… **decoded**: the span is `0..=max keyframe time over every element`, and an element **holds** its final pose β€” decoded, not assumed ([`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md), [`ui-record-loop-length.md`](structures/ui-record-loop-length.md)). Sensitivity over the 130 keyless full-screen primitives with an opaque interval: using the header's declared **`+0x08`** instead changes **0** verdicts (interchangeable); using the primitive's **own** last keyframe changes **72**; counting elements **gone** after their last keyframe changes **72**. πŸ”΄ So the hold decides **55 %** of verdicts β€” and dropping it is **refuted by a measured order**: `palogo_eff0.prm` is a single keyframe at t=0, so without the hold it is opaque for one instant, nothing else is up, and the rule calls it *free* against a game measured painting it first. βœ… The verdicts that matter are convention-independent β€” `pgloading_eff00.prm` is FIRST under all four, `pteff00.prm` FREE under all four. ⚠️ The port's **256 vs 211** was a **bundle mismatch, not a definitional one**: `palogo_eff0.prm` runs to t=255 on the publisher splash (entries 10/13) and t=210 on the developer (11/14) | +| [`structures/ui-clock-freezes-at-settle.md`](structures/ui-clock-freezes-at-settle.md) | The top-level clock stops at the settle point β€” observed in the running game | βœ… **measured**: `GP_TITLE` build 4 declares `t = 0…269`, about 120 presented frames at this run's pacing, and the dwell lasted **~1 100**. `ptcopyright` declares alphaβ‰₯1 for **106 units** (t=138…244) and is **drawn for 1 050 frames**; `ptlogo1` declares an exit at t=264 and is drawn for 1 095. Both vanish within three frames of the dwell ending. **The clock advances through the build-in, stops inside the settle window `[160,236]`, and holds; the exit ramp plays when the screen leaves, not on a timer** β€” [`ui-settle-time.md`](structures/ui-settle-time.md)'s decode observed from the other side. A nested record keeps looping on its own clock throughout. πŸ”΄ **This closes the 114-vs-120 gap, and it was my arithmetic**: 2.231 units/frame was regressed over *build-in* events (the only stretch the top-level clock advances) and applied to a period measured during the freeze β€” two different clocks. The declared **120** was never in doubt from the calibration-free dark-fraction test. βœ… The 51.158-frame period is now confirmed by a **second independent estimator** (autocorrelation, lag 51 with harmonics at 102/154) β€” ⚠️ whose first version **failed its control**, returning 48, because it indexed by sample position where the log's frame numbers have gaps. ❔ The **sweeps'** period stays unmeasured: the same validated estimator disagrees between two dwells of one screen (515 vs 452 frames). πŸ”΄ **Blocker: a single β’Ά on the title faults the guest** β€” 3 attempts, 2 register dumps of 223 MB and 519 MB, against 3 no-input runs that all completed; bounds menu-side dynamic RE here, and any scripted button press needs a `canary.stdout` size guard | diff --git a/docs/re/structures/ui-clock-freezes-at-settle.md b/docs/re/structures/ui-clock-freezes-at-settle.md new file mode 100644 index 00000000..1cc99390 --- /dev/null +++ b/docs/re/structures/ui-clock-freezes-at-settle.md @@ -0,0 +1,107 @@ +# The top-level clock freezes at the settle point β€” and that closes the 114-vs-120 gap + +**Classification: measured.** From the title draw capture of 2026-08-29 +(`ui_draw_capture.sh ARM=early`), re-read with the per-quad parser. + +## The observation + +`GP_TITLE` build 4 declares a timeline of `t = 0 … 269`. At this run's build-in +pacing that is about 120 presented frames. **The title dwell lasted ~1 100.** + +| element | declared alpha β‰₯ 1 | drawn in frames | span | +|---|---|---|---| +| `ptcopyright` | t = 138 … 244 (**106 units**) | 168 … 1217 | **1 050 frames** | +| `ptlogo1` | t = 26.6 … 264 (237 units) | 125 … 1220 | 1 095 frames | + +If the top-level clock ran on to the end of its declared timeline, `ptcopyright` +would fade out at t=244 β€” around frame 216 β€” and `ptlogo1` at t=264. **Neither +does.** Both are drawn continuously until the dwell ends, and disappear within +three frames of it. + +> **The top-level clock advances through the build-in, stops at the settle point, +> and holds there. The exit ramp is not played on a timer β€” it plays when +> something makes the screen leave.** + +That is [`ui-settle-time.md`](ui-settle-time.md)'s decode seen from the other +side, and observed in the running game rather than inferred from the file. The +freeze lands inside the settle window `[160, 236]`: `ptcopyright` reaches full +alpha at its start (t=160) and never moves again. + +Meanwhile the plate's focus record keeps looping throughout β€” a nested record runs +on **its own clock**, which does not stop when the parent's does. + +## πŸ”΄ What this closes: the 114-unit period, which was my error + +[`ui-record-loop-length.md`](ui-record-loop-length.md) recorded an unexplained gap: +the plate glow's period of **51.158 presented frames**, multiplied by **2.231** +units/frame, gives **114** units against a declared **120**. + +**The two numbers come from different clocks.** The 2.231 was regressed over five +*build-in* events β€” the only stretch in which the top-level clock advances. The +51.158 was measured over the settled dwell, where that clock is frozen and only +the record's own clock is running. Applying one to the other compares two +different stretches of wall time, and there was never a reason they should agree. + +The 120 was never in doubt from evidence that needs no conversion: the +**dark-fraction test** (17.7 % measured, 14.4 % predicted by 120, 2.2 % by 105, +with a culling threshold of 1 read off the data) refutes 105 by a factor of eight +without any units/frame at all. **The gap was an artefact of my arithmetic, not a +discrepancy in the decode**, and the doc no longer carries it as open. + +## βœ… The period itself, confirmed twice + +51.158 frames was originally measured by detecting cycle starts. A second, +independent estimator β€” autocorrelation of the glow's alpha series β€” returns +**lag 51**, with clean harmonics at **102** and **154**. + +⚠️ **And the first version of that estimator failed its control**, which is why it +is worth recording. Indexed by *sample position* it returned **48** for a period +known to be 51.158 β€” 6 % low, with the top three lags (47, 48, 49) not even +bracketing the truth. The log's frame numbers have gaps, so a lag of *n samples* +is not *n frames*. Indexed by frame number with missing frames masked, the control +passes. + +## ❔ What this does not settle: the sweeps' period + +The same validated estimator, applied to the two light sweeps, gives periods that +**disagree between two dwells of the same screen**: + +| sweep | dwell 1 | dwell 2 | +|---|---|---| +| tall quad, h β‰ˆ 1134 | **515** frames | **452** frames | +| tall quad, h β‰ˆ 1303 | 549 frames | 538 frames | + +A 14 % disagreement within one screen is not a period. The likely reason is that +the quad's x is a composite of the parent's placement with the leaf's rotation and +600–800 % scale, so "the quad's left edge" is not a clean phase variable β€” but +that is a hypothesis, not a finding. **The sweeps' period is unmeasured**, and the +`+0x08` field cannot settle it either: `ptloop01`/`ptloop02` have zero slack, and a +zero-slack record cannot distinguish "loops at 600" from "runs once and stops". + +## πŸ”΄ Blocker: a single β’Ά on the title faults the guest + +Three attempts to capture the **main menu** ended the same way. + +| run | input | outcome | +|---|---|---| +| 1 | β’Ά on title, then β’Ά again on the transition | guest fault, **519 MB** of register dump | +| 2 | one β’Ά on title (guard added) | drifted to a `flight` classification, 97 MB | +| 3 | one β’Ά on title | guest fault, **223 MB** of register dump | + +Against three runs in the same session that tapped nothing on the title and all +completed normally. This is the crash `ui_draw_capture.sh`'s own header records +from 2026-08-18 β€” "a stray A there sends the guest into the save-data probe". + +⚠️ **It bounds menu-side dynamic RE in this container**, and it is not a +contradiction of the corpus's existing menu measurements, which were taken before +and by some route that survived. What differs has not been found. + +⚠️ **And a guest fault writes an unbounded register dump to stdout** β€” 223 MB and +519 MB here, on a filesystem at 91 %. Any scripted run that presses a button needs +a size guard on `canary.stdout`. + +## Reproducing + +```bash +python3 tools/re-capture/quads_per_frame.py 200 1200 +``` diff --git a/docs/re/structures/ui-title-buildin-measured.md b/docs/re/structures/ui-title-buildin-measured.md index d3f13793..34c91e28 100644 --- a/docs/re/structures/ui-title-buildin-measured.md +++ b/docs/re/structures/ui-title-buildin-measured.md @@ -204,9 +204,13 @@ observed frame of five events against their declared keyframe times (residuals at **frame 106.1** β€” and the composite spike, which was not part of the fit, is frame **107**. -❔ **The glow's period implies a 114-unit cycle at that slope**, against a declared -120. The dark-fraction test below settles 120 against 105 without any calibration, -but the 5 % gap in the period is unexplained. +βœ… **The 114-unit gap is CLOSED, and it was my arithmetic.** The 2.231 units/frame +is regressed over *build-in* events β€” the only stretch in which the top-level clock +advances β€” while the 51.158-frame period is measured over the settled dwell, where +that clock is **frozen** and only the plate's own record is running. Two different +clocks; there was never a reason they should agree. The declared 120 was never in +doubt from the calibration-free dark-fraction test. See +[`ui-clock-freezes-at-settle.md`](ui-clock-freezes-at-settle.md). ❔ **The absolute frame rate of this run was not measured** β€” Canary logged no fps and the log has no timestamps β€” so nothing here is stated in seconds. It did not