diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index ec4b5597..57020a08 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -2533,7 +2533,35 @@ whatever it returns β€” the same reason a truncated log and a t=0 render both lo fine from inside. Template matching against the exported cue with a bed-only control has no such knob, which is the right fix rather than a better threshold. -## 🟑 2026-08-30 β€” the `.tbm` question is still open (two runs), and a driving trap you should know +## βœ… 2026-08-30 β€” a `.tbm` DRAWS. And `screen render` is wrong on any screen with one. + +**The `TUTORIAL` screen was reached and captured** +([capture](../re/captures/title-builds/live-tutorial-screen.png)). It has a +**full-screen blue circuit/hex background**. `GP_TUTORIAL` build 0's **element 0 is +`pubase.tbm`, pivot (640, 360)** β€” 1280Γ—720, the only full-screen *textured* element +in the bundle. Our render of the same build is the **identical layout on pure +black**: 6.0–6.4 % inked against the game's 99.7 % +([render](../re/captures/title-builds/render-tutorial-build0-for-comparison.png)). + +πŸ”΄ **Two things follow, and the second is the one that touches you:** + +1. `ui-forced-backdrop.md`'s surviving "inert" reading is **refuted** β€” its 24 + `.tbm` deciders are **correct**, not harmless. None is on your five screens, so + nothing you ship moves; but if the alpha-over assumption under that rule fails, + those 24 go for real. +2. **`sylpheed-cli screen render` silently omits the background of every screen + carrying a `.tbm`.** No diagnostic. If you ever diff against it outside the five + menu screens, that is a difference that is *ours*, not yours. + +⚠️ Reach: one `.tbm` observed. The class question β€” does a `.tbm` draw at all β€” is +settled; the ten other families are not individually seen. + +πŸ“Œ **And the driving trap from the failed runs is still worth having**: a 0.12 s β’Ά +issued while the guest is loading is **missed entirely** β€” 2 `[file-pad] vk=5800` +lines is *one* press. "The press did nothing" and "there was no press" are identical +on screen; only `[RE-INPUT]` separates them. Hold 0.5 s and confirm delivery. +[`tbm-submenu-not-reached.md`](../re/structures/tbm-submenu-not-reached.md) + Not something you need β€” none of the 24 `.tbm` deciders is on your five screens β€” but the *trap* generalises to anything driven. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 63f3d5d2..71eae5d6 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -174,6 +174,7 @@ files, which is how the same ground got covered twice. | [`structures/ui-title-buildin-measured.md`](structures/ui-title-buildin-measured.md) | The title's build-in and the plate glow, read out of the guest's own draw stream | βœ… **measured** (Canary, `ARM=early` draw capture): the decoded *mechanism* is observed, not just its end state. **The five flashes fire in a six-frame window and are absent from all 155 other sampled frames**; `ptlogo_back2eff1` is drawn in exactly 2 frames at **t = 54.0** against a decoded peak of **t54–56**, and `ptlogo1` first appears at **t = 42.2** against a decoded **t42** β€” with units/frame taken from the **glow's period alone**, a different element. The two holders (`ptlogo_back2eff`, `ptlogo_back2`) are continuous from frame 134. βœ… The glow's per-vertex colour alpha IS its fade alpha: **observed range 0…80 against a decoded peak of 80**, exact and unfitted; **period 51.158 presented frames** over 20 cycle starts; fitting the decoded ramp gives RMS **13.16** against **38.18 reversed** (2.9Γ—), so the asymmetry is real and correctly directed. Structure: the settled title is 10–11 draws naming no sprite β€” which is why arming at the title sees nothing. ⚠️ Frame **107** is a 27-draw spike between the movie's last frame and the title's first; calling it "the composite" was an **over-read** β€” it binds **no texture** and only 4 of its 27 draws log geometry. The second title entry has no such frame. ⚠️ The two entries are the same animation at **different sampling phases** (only 4 of 46 aligned frames match), which is what makes the `eff3` result robust. πŸ”΄πŸ”΄ **RETRACTED β€” the game DOES draw `ptlogo_back2eff3`, and all five flashes fire in both entries in the declared stagger** (`eff3` at frames 133–134 / 5957–5958, i.e. t=60.1 and 62.3, inside its declared t∈(58,64)). The absence was an **instrument artefact**: a draw batches several quads (`indices=8` is two) and the log dumps only the first 8 vertices, so min/max over a line **merges** them β€” and because the wipe is right-aligned, `eff3` (788…1196) lies entirely inside `eff4` (447…1196), making the union *exactly* `eff4`'s extent. The merged box matched `eff4` to 1 px. πŸ”΄ Three explanations had been "ruled out" and all three were aimed at the wrong failure β€” notably the invisible-draw check counted draws with **no** geometry, where the hiding place was **partial** geometry. Superseded text follows: ~~three alternative explanations tested and failed: *phase* (its window is **6 units** against a **2.23-unit** step, so it cannot be missed β€” frames 133/134 sit at t=60.1/62.3 inside it and draw `eff2` and `eff4` instead), *an unlogged draw* (exactly 2 blind draws/frame, always the same full-screen-triangle shader, present when no wipe is active), and *a bad position guess* (dropping position entirely, **zero** quads anywhere have a width within Β±30 of 408; the spectrum jumps 262 β†’ 748). Draw counts across both entries: eff1 **4**, eff2 **3**, eff3 **0**, eff4 **6**.~~ (all from the merged-box parse, and wrong) πŸ”΄ **The port draws `eff3` at t=60–62 and the console does not.** ❔ Why is not established β€” nothing in its element record differs from its neighbours. ⚠️ An earlier "sub-frame phase" explanation and the advice that drawing all five "shows more sweep than the console" are both **withdrawn**. ⚠️ What a frame-by-frame build-in comparison *will* show is disagreement about which flash lands in which frame β€” 2 units/submitted frame against this run's 2.231 units/presented frame β€” and neither side is wrong. πŸ”΄ **Trap:** matching a bound texture's dimensions to a sprite fails both ways β€” it missed every flash *and* read the intro movie's 640Γ—360 YUV planes as `ptbase2`. βœ… A regression of five events' observed frames against their declared times (residuals ≀0.9 frames) recovers the intercept at frame **106.1** when the composite spike, not in the fit, is frame **107**. ⚠️ Per-vertex alpha = fade alpha holds for the **glow** and does not generalise β€” `eff4` reads 255/127/254 on consecutive frames. ❔ Frame rate not recorded, so nothing is in seconds; the glow's period implies a **114**-unit cycle against a declared 120, unexplained; `eff5` vs `ptlogo_back2eff` not separated | | [`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/tbm-submenu-not-reached.md`](structures/tbm-submenu-not-reached.md) | Does a `.tbm` draw pixels in the running game? | βœ… **YES β€” measured 2026-08-30**, and it closes the surviving "inert" reading in [ui-forced-backdrop](structures/ui-forced-backdrop.md). The `TUTORIAL` screen was reached and captured: a **full-screen blue circuit/hex background**, where `GP_TUTORIAL` build 0's **element 0 is `pubase.tbm`, pivot (640,360)** = 1280Γ—720, the only full-screen *textured* element in the bundle (the other, `pueff00.prm`, is an untextured primitive the colour census puts at pure black). Our render of the same build is the **identical layout on pure black** β€” 6.0–6.4 % inked against the game's 99.7 %. So the rule's **24 `.tbm` deciders are correct, not harmless**, and πŸ”΄ **`screen render` silently omits the background of every screen carrying a `.tbm`**. ⚠️ One `.tbm` observed; the class question is settled, the ten other families are not. πŸ”΄ Getting there took three runs and cost two instrument failures worth reading: a 0.12 s β’Ά during a screen load is **never delivered** (`[RE-INPUT]` is the only witness), and **correlation cannot identify a screen when the candidate renders are near-blank** β€” masked correlation failed its control (picked `EXTRAS` over the known menu by 0.004), a high-passed variant passed by only 1.28Γ—, and **reading the title off the screen** settled it in one look | | [`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 | | [`structures/boot-splash-dwells-are-declared.md`](structures/boot-splash-dwells-are-declared.md) | How long each boot splash is shown | βœ… **decoded**: the dwells are the bundles' own declared timelines β€” publisher **t=0…255 = 4.250 s**, developer **t=0…210 = 3.500 s** at 60 units/s. The corpus's independent screenshot timing over 3 cold boots gives 4.30/4.60/4.37 and **3.51/3.50/3.37** β€” the developer agreeing to **1.1 %**, two of its three runs to 0.3 %. πŸ”΄ **Wall clock is the wrong unit to author**: a fresh no-input boot measured the same two dwells at **5.10–5.61 s** and 3.83–4.30 s, 15–20 % longer than both the declared values and the corpus's runs, on the same disc β€” so a seconds figure is one run's emulator pacing. Boundaries from the draw stream: publisher wordmark frames 6–119, **3 frames with no sprite drawn**, developer glows 123, wordmarks 140–209, intro video 216. πŸ”΄ **The frameβ†’wall-clock instrument resolves to one BUFFER FLUSH, not one frame** β€” 69 of 125 samples showed no advance and the rest jumped 7–15 frames, making the apparent rate swing 0.0164–0.0316 s/frame; frames 119 and 123 fall in one burst, so the inter-splash gap is **not separable** by it. Quoted as brackets; sub-flush estimates withdrawn before reporting. ⚠️ `palogo_anima` never appears β€” almost certainly the 8-vertex cap (7 elements batched, 2 logged), the same trap as the `eff3` false negative, so it is named not reported. ❔ the publisher's 4.1 % error vs the developer's 1.1 % is unexplained |